cloving 0.3.4 → 0.3.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/README.md +8 -2
- package/dist/cloving_gpt/adapters/claude.js +7 -5
- package/dist/cloving_gpt/adapters/gemini.js +14 -9
- package/dist/cloving_gpt/adapters/mistral.js +9 -7
- package/dist/cloving_gpt/adapters/ollama.js +7 -7
- package/dist/cloving_gpt/adapters/openai.js +9 -7
- package/dist/cloving_gpt/index.js +13 -8
- package/dist/commands/config.js +9 -9
- package/dist/commands/generate/context.js +13 -6
- package/dist/commands/generate/unit_tests.js +3 -261
- package/dist/commands/init.js +3 -218
- package/dist/commands/tokens.js +14 -6
- package/dist/index.js +5 -8
- package/dist/managers/BlockManager.js +121 -0
- package/dist/managers/ChatManager.js +202 -42
- package/dist/managers/ChunkManager.js +12 -0
- package/dist/managers/CodeManager.js +24 -10
- package/dist/managers/CommitManager.js +6 -3
- package/dist/managers/InitManager.js +216 -0
- package/dist/managers/ProxyManager.js +33 -10
- package/dist/managers/ReviewManager.js +40 -12
- package/dist/managers/ShellManager.js +7 -4
- package/dist/managers/UnitTestManager.js +243 -0
- package/dist/package.json +13 -8
- package/dist/utils/command_utils.js +86 -34
- package/dist/utils/config_utils.js +15 -5
- package/dist/utils/git_utils.js +7 -4
- package/dist/utils/prompt_utils.js +515 -0
- package/dist/utils/string_utils.js +119 -12
- package/package.json +13 -8
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const events_1 = require("events");
|
|
4
|
+
// Passes through string chunks, looking for code blocks, when it finds one, it starts buffering until it finds the end of the block
|
|
5
|
+
// Then it emits the code block and clears the buffer and continues
|
|
6
|
+
class BlockManager extends events_1.EventEmitter {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.buffer = '';
|
|
10
|
+
this.codeBuffer = '';
|
|
11
|
+
this.isBufferingCode = false;
|
|
12
|
+
this.isWaitingForContent = false;
|
|
13
|
+
}
|
|
14
|
+
// add content to the buffer, watching for code blocks along the way
|
|
15
|
+
addContent(content) {
|
|
16
|
+
// when getting ` , just add it to the buffer until we get a char that isn't `
|
|
17
|
+
if (content.includes('`')) {
|
|
18
|
+
this.isWaitingForContent = true;
|
|
19
|
+
this.buffer += content;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
else if (this.isWaitingForContent) {
|
|
23
|
+
content = this.buffer + content;
|
|
24
|
+
this.buffer = '';
|
|
25
|
+
}
|
|
26
|
+
const codeBlockMarker = '\n```';
|
|
27
|
+
let markerIndex = content.indexOf(codeBlockMarker);
|
|
28
|
+
if (content.startsWith('```'))
|
|
29
|
+
markerIndex = 0;
|
|
30
|
+
if (markerIndex !== -1) {
|
|
31
|
+
if (this.isBufferingCode) {
|
|
32
|
+
// End of code block
|
|
33
|
+
this.codeBuffer += content.slice(0, markerIndex + codeBlockMarker.length);
|
|
34
|
+
const rest = content.slice(markerIndex + codeBlockMarker.length);
|
|
35
|
+
this.emit('endGeneratingCode');
|
|
36
|
+
this.emitCodeBlock();
|
|
37
|
+
this.emit('content', rest);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Start of code block
|
|
41
|
+
this.emit('startGeneratingCode');
|
|
42
|
+
this.emitBuffer(content.slice(0, markerIndex));
|
|
43
|
+
this.codeBuffer = content.slice(markerIndex);
|
|
44
|
+
this.isBufferingCode = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// No code block marker found
|
|
49
|
+
if (this.isBufferingCode) {
|
|
50
|
+
this.codeBuffer += content;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.buffer += content;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!this.isBufferingCode && this.buffer) {
|
|
57
|
+
this.emitBuffer();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
emitBuffer(additionalContent = '') {
|
|
61
|
+
const contentToEmit = this.buffer + additionalContent;
|
|
62
|
+
if (contentToEmit) {
|
|
63
|
+
this.emit('content', contentToEmit);
|
|
64
|
+
this.clearBuffer();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
emitCodeBlock() {
|
|
68
|
+
if (this.codeBuffer) {
|
|
69
|
+
const codeParts = this.parseCodeBuffer();
|
|
70
|
+
if (codeParts) {
|
|
71
|
+
const { language, currentStart, newEnd, currentCode, newCode } = codeParts;
|
|
72
|
+
this.emit('codeBlock', {
|
|
73
|
+
language,
|
|
74
|
+
currentStart,
|
|
75
|
+
newEnd,
|
|
76
|
+
currentCode,
|
|
77
|
+
newCode,
|
|
78
|
+
raw: this.codeBuffer,
|
|
79
|
+
});
|
|
80
|
+
this.emit('content', this.buffer);
|
|
81
|
+
this.clearBuffer();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parses a code buffer into its constituent parts.
|
|
87
|
+
*
|
|
88
|
+
* This function takes a code buffer as a string, splits it into lines, and identifies the language,
|
|
89
|
+
* current start index, divider index, and new end index. It then extracts the current code and new code
|
|
90
|
+
* from the buffer based on these indices.
|
|
91
|
+
*
|
|
92
|
+
* @return {{ language: string, currentStart: string, newEnd: string, current: string, newCode: string, afterCode: string }}
|
|
93
|
+
* An object containing the language, current start, new end, current code, and new code.
|
|
94
|
+
*/
|
|
95
|
+
parseCodeBuffer() {
|
|
96
|
+
const lines = this.codeBuffer.split('\n');
|
|
97
|
+
const language = lines[0].trim().slice(3); // Remove '```' from the language line
|
|
98
|
+
const currentStartIndex = lines.findIndex((line) => line.startsWith('<<<<<<< CURRENT'));
|
|
99
|
+
const dividerIndex = lines.findIndex((line) => line === '=======');
|
|
100
|
+
const newEndIndex = lines.findIndex((line) => line.startsWith('>>>>>>> NEW'));
|
|
101
|
+
if (currentStartIndex === -1 || dividerIndex === -1 || newEndIndex === -1) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const currentCode = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
|
|
105
|
+
const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
|
|
106
|
+
return {
|
|
107
|
+
language,
|
|
108
|
+
currentStart: lines[currentStartIndex],
|
|
109
|
+
newEnd: lines[newEndIndex],
|
|
110
|
+
currentCode,
|
|
111
|
+
newCode,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
clearBuffer() {
|
|
115
|
+
this.buffer = '';
|
|
116
|
+
this.codeBuffer = '';
|
|
117
|
+
this.isBufferingCode = false;
|
|
118
|
+
this.isWaitingForContent = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.default = BlockManager;
|
|
@@ -9,13 +9,16 @@ const child_process_1 = require("child_process");
|
|
|
9
9
|
const colors_1 = __importDefault(require("colors"));
|
|
10
10
|
const axios_1 = __importDefault(require("axios"));
|
|
11
11
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
12
14
|
const CommitManager_1 = __importDefault(require("./CommitManager"));
|
|
13
15
|
const ChunkManager_1 = __importDefault(require("./ChunkManager"));
|
|
16
|
+
const BlockManager_1 = __importDefault(require("./BlockManager"));
|
|
14
17
|
const ReviewManager_1 = __importDefault(require("./ReviewManager"));
|
|
15
18
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
16
19
|
const string_utils_1 = require("../utils/string_utils");
|
|
17
20
|
const config_utils_1 = require("../utils/config_utils");
|
|
18
|
-
const
|
|
21
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
19
22
|
const specialCommands = [
|
|
20
23
|
'save',
|
|
21
24
|
'commit',
|
|
@@ -27,7 +30,7 @@ const specialCommands = [
|
|
|
27
30
|
'ls <pattern>',
|
|
28
31
|
'git <command>',
|
|
29
32
|
'help',
|
|
30
|
-
'exit'
|
|
33
|
+
'exit',
|
|
31
34
|
];
|
|
32
35
|
class ChatManager {
|
|
33
36
|
/**
|
|
@@ -56,6 +59,7 @@ class ChatManager {
|
|
|
56
59
|
historySize: 1000,
|
|
57
60
|
});
|
|
58
61
|
this.chunkManager = new ChunkManager_1.default();
|
|
62
|
+
this.blockManager = new BlockManager_1.default();
|
|
59
63
|
}
|
|
60
64
|
/**
|
|
61
65
|
* Initializes the ChatManager by loading context files, displaying a welcome message,
|
|
@@ -87,16 +91,16 @@ class ChatManager {
|
|
|
87
91
|
console.log(` - ${colors_1.default.yellow(colors_1.default.bold('exit'))} Quit this session`);
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
* Checks for the latest version of the app from GitHub and compares it with the current version.
|
|
95
|
+
* If a new version is available, it notifies the user.
|
|
96
|
+
* @private
|
|
97
|
+
* @returns {Promise<void>}
|
|
98
|
+
*/
|
|
95
99
|
async checkForLatestVersion() {
|
|
96
100
|
try {
|
|
97
101
|
const response = await axios_1.default.get('https://api.github.com/repos/cloving-ai/cloving-cli/releases/latest');
|
|
98
102
|
const latestVersion = response.data.tag_name;
|
|
99
|
-
const currentVersion = `v${(0,
|
|
103
|
+
const currentVersion = `v${(0, prompt_utils_1.getPackageVersion)()}`;
|
|
100
104
|
if (latestVersion !== currentVersion) {
|
|
101
105
|
console.log(colors_1.default.bgWhite.black(`\n 🚀 A new version of Cloving is available: ${latestVersion} `));
|
|
102
106
|
console.log(colors_1.default.bgWhite.black(` You are currently using version: ${currentVersion} `));
|
|
@@ -123,9 +127,9 @@ class ChatManager {
|
|
|
123
127
|
*/
|
|
124
128
|
async loadContextFiles() {
|
|
125
129
|
const config = (0, config_utils_1.getClovingConfig)();
|
|
126
|
-
const primaryLanguage = config.languages.find(lang => lang.primary);
|
|
130
|
+
const primaryLanguage = config.languages.find((lang) => lang.primary);
|
|
127
131
|
const defaultDirectory = primaryLanguage ? primaryLanguage.directory : '.';
|
|
128
|
-
const testingDirectories = config.testingFrameworks?.map(framework => framework.directory) || [];
|
|
132
|
+
const testingDirectories = config.testingFrameworks?.map((framework) => framework.directory) || [];
|
|
129
133
|
const testingDirectory = testingDirectories[0];
|
|
130
134
|
let files = this.options.files || [defaultDirectory, testingDirectory].filter(Boolean);
|
|
131
135
|
if (files.length > 0) {
|
|
@@ -136,7 +140,7 @@ class ChatManager {
|
|
|
136
140
|
if (!file)
|
|
137
141
|
continue;
|
|
138
142
|
const previousCount = Object.keys(this.contextFiles).length;
|
|
139
|
-
this.contextFiles = await (0,
|
|
143
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
|
|
140
144
|
const newCount = Object.keys(this.contextFiles).length;
|
|
141
145
|
const addedCount = newCount - previousCount;
|
|
142
146
|
const totalTokens = this.calculateTotalTokens();
|
|
@@ -235,6 +239,13 @@ class ChatManager {
|
|
|
235
239
|
* - Any other input is processed as a user query to the AI.
|
|
236
240
|
*/
|
|
237
241
|
async handleCommand(command) {
|
|
242
|
+
// Check if the command is a single character and map it to the first matching special command
|
|
243
|
+
if (command.length === 1) {
|
|
244
|
+
const matchingCommand = specialCommands.find((cmd) => cmd.startsWith(command));
|
|
245
|
+
if (matchingCommand) {
|
|
246
|
+
command = matchingCommand.split(' ')[0]; // Use only the command part without arguments
|
|
247
|
+
}
|
|
248
|
+
}
|
|
238
249
|
switch (command) {
|
|
239
250
|
case 'help':
|
|
240
251
|
this.displayHelp();
|
|
@@ -249,10 +260,10 @@ class ChatManager {
|
|
|
249
260
|
await this.handleCommit();
|
|
250
261
|
break;
|
|
251
262
|
case 'ls':
|
|
252
|
-
this.handleList(
|
|
263
|
+
this.handleList('ls *');
|
|
253
264
|
break;
|
|
254
265
|
case 'rm':
|
|
255
|
-
await this.handleRemove(
|
|
266
|
+
await this.handleRemove('rm *');
|
|
256
267
|
break;
|
|
257
268
|
case 'review':
|
|
258
269
|
await this.handleReview();
|
|
@@ -292,7 +303,7 @@ class ChatManager {
|
|
|
292
303
|
}
|
|
293
304
|
else {
|
|
294
305
|
for (const filePath of foundFiles) {
|
|
295
|
-
this.contextFiles = await (0,
|
|
306
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
|
|
296
307
|
const content = this.contextFiles[filePath];
|
|
297
308
|
const tokenEstimate = this.estimateTokens(content);
|
|
298
309
|
console.log(`\nAdded ${colors_1.default.bold(colors_1.default.green(filePath))} to this chat session's context (${colors_1.default.yellow(`~${tokenEstimate.toLocaleString()} tokens`)})`);
|
|
@@ -317,8 +328,10 @@ class ChatManager {
|
|
|
317
328
|
if (error) {
|
|
318
329
|
return reject(error);
|
|
319
330
|
}
|
|
320
|
-
const files = stdout
|
|
321
|
-
.
|
|
331
|
+
const files = stdout
|
|
332
|
+
.split('\n')
|
|
333
|
+
.filter((filePath) => filePath.trim() !== '')
|
|
334
|
+
.map((filePath) => (filePath.startsWith('./') ? filePath.slice(2) : filePath));
|
|
322
335
|
resolve(files);
|
|
323
336
|
});
|
|
324
337
|
});
|
|
@@ -337,7 +350,7 @@ class ChatManager {
|
|
|
337
350
|
const filePath = command.slice(4).trim();
|
|
338
351
|
if (filePath) {
|
|
339
352
|
try {
|
|
340
|
-
this.contextFiles = await (0,
|
|
353
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
|
|
341
354
|
console.log(`\nAdded ${colors_1.default.bold(colors_1.default.green(filePath))} to this chat session's context`);
|
|
342
355
|
this.refreshContext();
|
|
343
356
|
}
|
|
@@ -350,11 +363,23 @@ class ChatManager {
|
|
|
350
363
|
}
|
|
351
364
|
this.rl.prompt();
|
|
352
365
|
}
|
|
353
|
-
refreshContext() {
|
|
354
|
-
|
|
366
|
+
async refreshContext() {
|
|
367
|
+
await this.reloadContextFiles();
|
|
368
|
+
const updatedSystemPrompt = (0, prompt_utils_1.generateCodegenPrompt)(this.contextFiles);
|
|
355
369
|
this.chatHistory[0] = { role: 'user', content: updatedSystemPrompt };
|
|
356
370
|
this.chatHistory[1] = { role: 'assistant', content: 'What would you like to do?' };
|
|
357
371
|
}
|
|
372
|
+
async reloadContextFiles() {
|
|
373
|
+
for (const filePath in this.contextFiles) {
|
|
374
|
+
try {
|
|
375
|
+
const content = await fs_1.default.promises.readFile(path_1.default.resolve(filePath), 'utf8');
|
|
376
|
+
this.contextFiles[filePath] = content;
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
console.error(`Error reading file ${filePath}:`, error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
358
383
|
isRemoveCommand(command) {
|
|
359
384
|
return command.startsWith('rm ');
|
|
360
385
|
}
|
|
@@ -367,14 +392,14 @@ class ChatManager {
|
|
|
367
392
|
// Function to filter paths using the pattern
|
|
368
393
|
filterPaths(paths, pattern) {
|
|
369
394
|
const regex = this.globToRegExp(pattern);
|
|
370
|
-
return paths.filter(path => regex.test(path));
|
|
395
|
+
return paths.filter((path) => regex.test(path));
|
|
371
396
|
}
|
|
372
397
|
async handleRemove(command) {
|
|
373
398
|
const pattern = command.slice(3).trim();
|
|
374
399
|
if (pattern) {
|
|
375
400
|
const matchedFiles = this.filterPaths(Object.keys(this.contextFiles), pattern);
|
|
376
401
|
if (matchedFiles.length > 0) {
|
|
377
|
-
matchedFiles.forEach(filePath => {
|
|
402
|
+
matchedFiles.forEach((filePath) => {
|
|
378
403
|
delete this.contextFiles[filePath];
|
|
379
404
|
console.log(`Removed ${colors_1.default.bold(colors_1.default.green(filePath))} from this chat session's context files`);
|
|
380
405
|
});
|
|
@@ -402,7 +427,7 @@ class ChatManager {
|
|
|
402
427
|
if (matchedFiles.length > 0) {
|
|
403
428
|
console.log(`\nFiles in the current chat session context:`);
|
|
404
429
|
let totalTokens = 0;
|
|
405
|
-
matchedFiles.forEach(fileName => {
|
|
430
|
+
matchedFiles.forEach((fileName) => {
|
|
406
431
|
const content = this.contextFiles[fileName];
|
|
407
432
|
const tokenEstimate = this.estimateTokens(content);
|
|
408
433
|
totalTokens += tokenEstimate;
|
|
@@ -417,7 +442,7 @@ class ChatManager {
|
|
|
417
442
|
this.rl.prompt();
|
|
418
443
|
}
|
|
419
444
|
async handleCopy() {
|
|
420
|
-
const lastResponse = this.chatHistory.filter(msg => msg.role === 'assistant').pop();
|
|
445
|
+
const lastResponse = this.chatHistory.filter((msg) => msg.role === 'assistant').pop();
|
|
421
446
|
if (lastResponse) {
|
|
422
447
|
copy_paste_1.default.copy(lastResponse.content, () => {
|
|
423
448
|
console.info('Last response copied to clipboard.');
|
|
@@ -443,7 +468,10 @@ class ChatManager {
|
|
|
443
468
|
this.rl.prompt();
|
|
444
469
|
}
|
|
445
470
|
async handleSave() {
|
|
446
|
-
const lastResponse = this.chatHistory
|
|
471
|
+
const lastResponse = this.chatHistory
|
|
472
|
+
.slice()
|
|
473
|
+
.reverse()
|
|
474
|
+
.find((msg) => msg.role === 'assistant');
|
|
447
475
|
if (lastResponse) {
|
|
448
476
|
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(lastResponse.content);
|
|
449
477
|
if (Object.keys(currentNewBlocks).length > 0) {
|
|
@@ -493,12 +521,15 @@ class ChatManager {
|
|
|
493
521
|
this.isProcessing = true;
|
|
494
522
|
this.chunkManager = new ChunkManager_1.default();
|
|
495
523
|
try {
|
|
496
|
-
this.refreshContext();
|
|
524
|
+
await this.refreshContext();
|
|
497
525
|
this.prompt = this.generatePrompt(input);
|
|
498
526
|
this.addChatHistory(this.prompt);
|
|
499
527
|
if (!this.isSilent) {
|
|
500
|
-
const fullPrompt = this.chatHistory
|
|
501
|
-
|
|
528
|
+
const fullPrompt = this.chatHistory
|
|
529
|
+
.map((m) => {
|
|
530
|
+
const content = m.content
|
|
531
|
+
.split('\n')
|
|
532
|
+
.map((line) => {
|
|
502
533
|
if (line.startsWith('###')) {
|
|
503
534
|
return colors_1.default.red.bold(line);
|
|
504
535
|
}
|
|
@@ -506,21 +537,27 @@ class ChatManager {
|
|
|
506
537
|
return colors_1.default.yellow.bold(line);
|
|
507
538
|
}
|
|
508
539
|
return line;
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
|
|
540
|
+
})
|
|
541
|
+
.join('\n');
|
|
542
|
+
return m.role === 'user'
|
|
543
|
+
? `${colors_1.default.yellow.bold('## User Prompt')}\n\n${content}`
|
|
544
|
+
: `${colors_1.default.green.bold("## 🍀 Cloving's Response 🍀")}\n\n${content}`;
|
|
545
|
+
})
|
|
546
|
+
.join('\n\n');
|
|
512
547
|
// Review the prompt using the REPL
|
|
513
548
|
const tokenCount = Math.ceil(fullPrompt.length / 4).toLocaleString();
|
|
514
|
-
console.log(
|
|
549
|
+
console.log(`${colors_1.default.yellow.bold('INFORMATION')} The generated prompt is approximately ${tokenCount} tokens long. Would you like to review the prompt before sending it? ${colors_1.default.gray('(Y/n)')}`);
|
|
515
550
|
const reviewPrompt = await new Promise((resolve) => {
|
|
516
551
|
this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
|
|
517
552
|
resolve(answer.trim().toLowerCase());
|
|
518
553
|
});
|
|
519
554
|
});
|
|
520
|
-
if (reviewPrompt
|
|
555
|
+
if (reviewPrompt.startsWith('y') || reviewPrompt === '') {
|
|
521
556
|
console.log(colors_1.default.gray.bold('\n---------------------- PROMPT START ----------------------\n'));
|
|
522
557
|
const promptParts = fullPrompt.split('\n```');
|
|
523
|
-
const highlightedPrompt = promptParts
|
|
558
|
+
const highlightedPrompt = promptParts
|
|
559
|
+
.map((part, index) => index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part)
|
|
560
|
+
.join('\n```');
|
|
524
561
|
console.log(highlightedPrompt);
|
|
525
562
|
console.log(colors_1.default.gray.bold('\n---------------------- PROMPT END ----------------------\n'));
|
|
526
563
|
console.log(`Do you want to proceed with this prompt? ${colors_1.default.gray('(Y/n)')}`);
|
|
@@ -529,7 +566,7 @@ class ChatManager {
|
|
|
529
566
|
resolve(answer.trim().toLowerCase());
|
|
530
567
|
});
|
|
531
568
|
});
|
|
532
|
-
if (confirmPrompt
|
|
569
|
+
if (!confirmPrompt.startsWith('y') && confirmPrompt !== '') {
|
|
533
570
|
console.log('Operation cancelled by user.');
|
|
534
571
|
this.isProcessing = false;
|
|
535
572
|
this.rl.prompt();
|
|
@@ -537,7 +574,10 @@ class ChatManager {
|
|
|
537
574
|
}
|
|
538
575
|
}
|
|
539
576
|
}
|
|
540
|
-
const responseStream = await this.gpt.streamText({
|
|
577
|
+
const responseStream = await this.gpt.streamText({
|
|
578
|
+
prompt: this.prompt,
|
|
579
|
+
messages: this.chatHistory,
|
|
580
|
+
});
|
|
541
581
|
let accumulatedContent = '';
|
|
542
582
|
this.handleResponseStream(responseStream, accumulatedContent);
|
|
543
583
|
}
|
|
@@ -548,25 +588,69 @@ class ChatManager {
|
|
|
548
588
|
addChatHistory(input) {
|
|
549
589
|
this.chatHistory.push({ role: 'user', content: input });
|
|
550
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Handles the streaming response from the AI model.
|
|
593
|
+
* This method processes the incoming data chunks, converts them to readable output,
|
|
594
|
+
* and manages the accumulated content.
|
|
595
|
+
*
|
|
596
|
+
* @param responseStream - The stream of response data from the AI model.
|
|
597
|
+
* @param accumulatedContent - A string to accumulate the complete response content.
|
|
598
|
+
*/
|
|
551
599
|
handleResponseStream(responseStream, accumulatedContent) {
|
|
600
|
+
this.blockManager.on('content', (content) => {
|
|
601
|
+
process.stdout.write('\n------ beginning of blockManager content ------\n');
|
|
602
|
+
process.stdout.write(content);
|
|
603
|
+
process.stdout.write('\n------ end of blockManager content ------\n');
|
|
604
|
+
accumulatedContent += content;
|
|
605
|
+
});
|
|
606
|
+
this.blockManager.on('codeBlock', (codeBlock) => {
|
|
607
|
+
process.stdout.write(codeBlock.language);
|
|
608
|
+
process.stdout.write('\n');
|
|
609
|
+
process.stdout.write(codeBlock.currentStart);
|
|
610
|
+
process.stdout.write('\n');
|
|
611
|
+
process.stdout.write((0, cli_highlight_1.default)(codeBlock.currentCode, { language: codeBlock.language }));
|
|
612
|
+
process.stdout.write('\n=======\n');
|
|
613
|
+
process.stdout.write((0, cli_highlight_1.default)(codeBlock.newCode, { language: codeBlock.language }));
|
|
614
|
+
process.stdout.write('\n');
|
|
615
|
+
process.stdout.write(codeBlock.newEnd);
|
|
616
|
+
accumulatedContent += codeBlock.raw;
|
|
617
|
+
});
|
|
618
|
+
// Handle content chunks from the ChunkManager
|
|
552
619
|
this.chunkManager.on('content', (buffer) => {
|
|
553
620
|
let convertedStream = this.gpt.convertStream(buffer);
|
|
554
621
|
while (convertedStream !== null) {
|
|
555
622
|
const { output, lastChar } = convertedStream;
|
|
623
|
+
process.stdout.write('\n------ beginning of chunk ------\n');
|
|
556
624
|
process.stdout.write(output);
|
|
557
|
-
|
|
625
|
+
process.stdout.write(`lastChar: ${lastChar}\n`);
|
|
626
|
+
process.stdout.write('\n------ end of chunk ------\n');
|
|
627
|
+
// buffer the output to the BlockManager
|
|
628
|
+
this.blockManager.addContent(output);
|
|
629
|
+
// clear the buffer from the ChunkManager
|
|
558
630
|
this.chunkManager.clearBuffer(lastChar);
|
|
631
|
+
// Try to convert the rest of the buffer to a string
|
|
559
632
|
buffer = buffer.slice(lastChar);
|
|
560
633
|
convertedStream = this.gpt.convertStream(buffer);
|
|
561
634
|
}
|
|
562
635
|
});
|
|
636
|
+
// Handle incoming data chunks from the response stream
|
|
563
637
|
responseStream.data.on('data', (chunk) => {
|
|
638
|
+
// Convert the chunk to a string and add it to the ChunkManager
|
|
639
|
+
if (!chunk)
|
|
640
|
+
return;
|
|
641
|
+
// a chunk is a JSON object with different keys depending on the AI service provider
|
|
564
642
|
const chunkString = chunk.toString();
|
|
643
|
+
process.stdout.write('\n------ beginning of chunk string ------\n');
|
|
644
|
+
process.stdout.write(chunkString);
|
|
645
|
+
process.stdout.write('\n------ end of chunk string ------\n');
|
|
565
646
|
this.chunkManager.addChunk(chunkString);
|
|
566
647
|
});
|
|
648
|
+
// Handle the end of the response stream
|
|
567
649
|
responseStream.data.on('end', () => {
|
|
650
|
+
// Finalize the response when the stream ends
|
|
568
651
|
this.finalizeResponse(accumulatedContent);
|
|
569
652
|
});
|
|
653
|
+
// Handle any errors in the response stream
|
|
570
654
|
responseStream.data.on('error', (error) => {
|
|
571
655
|
console.error('Error streaming response:', error);
|
|
572
656
|
this.isProcessing = false;
|
|
@@ -574,9 +658,84 @@ class ChatManager {
|
|
|
574
658
|
this.rl.prompt();
|
|
575
659
|
});
|
|
576
660
|
}
|
|
577
|
-
|
|
661
|
+
processOutput(output, accumulatedContent, codeBuffer, isBufferingCode) {
|
|
662
|
+
const codeBlockMarker = '\n```';
|
|
663
|
+
let currentIndex = 0;
|
|
664
|
+
while (currentIndex < output.length) {
|
|
665
|
+
let markerIndex = output.indexOf(codeBlockMarker, currentIndex);
|
|
666
|
+
if (output.startsWith('```'))
|
|
667
|
+
markerIndex = 0;
|
|
668
|
+
// Check if the code block marker is found
|
|
669
|
+
if (markerIndex !== -1) {
|
|
670
|
+
if (isBufferingCode) {
|
|
671
|
+
// End of code block
|
|
672
|
+
codeBuffer += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
|
|
673
|
+
const { language, currentStart, newEnd, current: currentCode, new: newCode } = this.parseCodeBuffer(codeBuffer);
|
|
674
|
+
if (language)
|
|
675
|
+
process.stdout.write(language);
|
|
676
|
+
if (currentStart)
|
|
677
|
+
process.stdout.write(currentStart);
|
|
678
|
+
if (currentCode)
|
|
679
|
+
process.stdout.write((0, cli_highlight_1.default)(currentCode, { language }));
|
|
680
|
+
process.stdout.write('=======');
|
|
681
|
+
if (newCode)
|
|
682
|
+
process.stdout.write((0, cli_highlight_1.default)(newCode, { language }));
|
|
683
|
+
if (newEnd)
|
|
684
|
+
process.stdout.write(newEnd);
|
|
685
|
+
accumulatedContent += codeBuffer;
|
|
686
|
+
codeBuffer = '';
|
|
687
|
+
isBufferingCode = false;
|
|
688
|
+
currentIndex = markerIndex + codeBlockMarker.length;
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
// Start of code block
|
|
692
|
+
process.stdout.write(output.slice(currentIndex, markerIndex + codeBlockMarker.length));
|
|
693
|
+
accumulatedContent += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
|
|
694
|
+
codeBuffer = '';
|
|
695
|
+
isBufferingCode = true;
|
|
696
|
+
currentIndex = markerIndex + codeBlockMarker.length;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
// No code block marker found
|
|
701
|
+
if (isBufferingCode) {
|
|
702
|
+
// buffering code
|
|
703
|
+
codeBuffer += output.slice(currentIndex);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
process.stdout.write(output.slice(currentIndex));
|
|
707
|
+
accumulatedContent += output.slice(currentIndex);
|
|
708
|
+
}
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return [isBufferingCode, codeBuffer, accumulatedContent];
|
|
713
|
+
}
|
|
714
|
+
parseCodeBuffer(codeBuffer) {
|
|
715
|
+
const lines = codeBuffer.split('\n');
|
|
716
|
+
const language = lines[0].trim();
|
|
717
|
+
const currentStartIndex = lines.findIndex(line => line.startsWith('<<<<<<< CURRENT'));
|
|
718
|
+
const dividerIndex = lines.findIndex(line => line === '=======');
|
|
719
|
+
const newEndIndex = lines.findIndex(line => line.startsWith('>>>>>>> NEW'));
|
|
720
|
+
const current = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
|
|
721
|
+
const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
|
|
722
|
+
return { language, currentStart: lines[currentStartIndex], newEnd: lines[newEndIndex], current, new: newCode };
|
|
723
|
+
}
|
|
724
|
+
async finalizeResponse(accumulatedContent) {
|
|
578
725
|
this.chatHistory.push({ role: 'assistant', content: accumulatedContent.trim() });
|
|
579
726
|
this.isProcessing = false;
|
|
727
|
+
if (accumulatedContent) {
|
|
728
|
+
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(accumulatedContent);
|
|
729
|
+
const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
|
|
730
|
+
if (!canApply) {
|
|
731
|
+
console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied, prompting the AI to try again with more detail.\n`);
|
|
732
|
+
this.processUserInput(`Some of the provided code blocks could not be applied,
|
|
733
|
+
please match the existing code with a few more lines of context and make sure it is a character for character exact match.
|
|
734
|
+
|
|
735
|
+
${summary}`);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
580
739
|
process.stdout.write(`
|
|
581
740
|
|
|
582
741
|
You can follow up with another request or:
|
|
@@ -598,20 +757,21 @@ You can follow up with another request or:
|
|
|
598
757
|
const errorNumber = error.response?.status || 'unknown';
|
|
599
758
|
switch (errorNumber) {
|
|
600
759
|
case 400:
|
|
601
|
-
errorMessage =
|
|
760
|
+
errorMessage = 'Invalid model or prompt size too large. Try specifying fewer files.';
|
|
602
761
|
break;
|
|
603
762
|
case 403:
|
|
604
|
-
errorMessage =
|
|
763
|
+
errorMessage = 'Inactive subscription or usage limit reached';
|
|
605
764
|
break;
|
|
606
765
|
case 429:
|
|
607
|
-
errorMessage =
|
|
766
|
+
errorMessage = 'Rate limit error';
|
|
608
767
|
break;
|
|
609
768
|
case 500:
|
|
610
|
-
errorMessage =
|
|
769
|
+
errorMessage = 'Internal server error';
|
|
611
770
|
break;
|
|
612
771
|
}
|
|
613
772
|
const promptTokens = Math.ceil(this.prompt.length / 4).toLocaleString();
|
|
614
|
-
|
|
773
|
+
const statusMessage = error.response?.data?.statusMessage;
|
|
774
|
+
console.error(colors_1.default.red(`\nError (${errorNumber}) while submitting the prompt to the AI API\n\n${statusMessage}\n`));
|
|
615
775
|
this.isProcessing = false;
|
|
616
776
|
this.rl.prompt();
|
|
617
777
|
}
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const events_1 = require("events");
|
|
4
|
+
// A chunk is a JSON object with different keys depending on the AI service provider
|
|
5
|
+
//
|
|
6
|
+
// Some providers produce single response in a single chunk
|
|
7
|
+
//
|
|
8
|
+
// Others produce multiple responses in a single chunk
|
|
9
|
+
//
|
|
10
|
+
// This Chunk Manager's job is to buffer the chunks and emit them as a single string
|
|
11
|
+
// without automatically clearing the buffer until clearBuffer is called.
|
|
12
|
+
// That way, the consumer of the chunk can process it and clear the buffer after
|
|
13
|
+
// they have received enough data.
|
|
4
14
|
class ChunkManager extends events_1.EventEmitter {
|
|
5
15
|
constructor() {
|
|
6
16
|
super(...arguments);
|
|
7
17
|
this.buffer = '';
|
|
8
18
|
}
|
|
9
19
|
addChunk(chunk) {
|
|
20
|
+
if (!chunk)
|
|
21
|
+
return;
|
|
10
22
|
this.buffer += chunk;
|
|
11
23
|
this.emit('content', this.buffer);
|
|
12
24
|
}
|