From d7505d5a60ee1b143cf5bbd6218242ed905040de Mon Sep 17 00:00:00 2001 From: Dipam Sen Date: Tue, 2 Apr 2024 22:57:10 +0530 Subject: [PATCH 1/9] safety error handling and other improvements --- src/lib/agent/Agent.js | 17 +++++++++++++---- src/lib/agent/providers/geminiProvider.js | 8 ++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lib/agent/Agent.js b/src/lib/agent/Agent.js index 30f1b80..b3f1c6d 100644 --- a/src/lib/agent/Agent.js +++ b/src/lib/agent/Agent.js @@ -16,6 +16,7 @@ class Agent { this.isNewPrompt = false; this.promptingTemplate = 'Dan says: {prompt}\nCurrent code in the editor:\n```\n{currentCode}\n```'; + this.isStreaming = false; } /** @@ -23,18 +24,26 @@ class Agent { * @param {string} input The prompt to be processed */ prompt(input) { + if (this.isStreaming || this.actionsQueue.length > 0) { + return vscode.window.showErrorMessage( + 'Please wait for the current prompt to finish processing before sending another one.' + ); + } const editor = vscode.window.activeTextEditor; this.isNewPrompt = true; const prompt = this.promptingTemplate .replace('{prompt}', input) .replace('{currentCode}', editor.document.getText()); - this.provider.queryStream(prompt, (response) => - this.consumeStream(response) - ); + this.isStreaming = true; + this.provider + .queryStream(prompt, (response) => this.consumeStream(response)) + .then(() => { + this.isStreaming = false; + }); } - async consumeStream(response) { + consumeStream(response) { const text = response.response; const event = response.event; diff --git a/src/lib/agent/providers/geminiProvider.js b/src/lib/agent/providers/geminiProvider.js index 044f328..cb5cf75 100644 --- a/src/lib/agent/providers/geminiProvider.js +++ b/src/lib/agent/providers/geminiProvider.js @@ -2,6 +2,7 @@ const ModelProvider = require('./genericProvider'); const { prompts } = require('../../../prompt'); const config = require('../../../../config'); const { GoogleGenerativeAI } = require('@google/generative-ai'); +const vscode = require('vscode'); class GeminiProvider extends ModelProvider { constructor() { @@ -47,6 +48,13 @@ class GeminiProvider extends ModelProvider { const result = await this.chat.sendMessageStream(prompt); let fullResponse = ''; for await (const chunk of result.stream) { + if (chunk.promptFeedback?.blockReason) { + // prompt blocked + this.messageHistory.pop(); + return vscode.window.showErrorMessage( + `Prompt blocked due to ${chunk.promptFeedback.blockReason}` + ); + } const text = chunk.text(); fullResponse += text; await process({ response: text, event: 'output' }); From b8f86f356cbf14a38edd9d8cfa9454a5026c2d77 Mon Sep 17 00:00:00 2001 From: Dipam Sen Date: Tue, 2 Apr 2024 23:17:01 +0530 Subject: [PATCH 2/9] lift vscode api from provider --- src/lib/agent/Agent.js | 6 ++++-- src/lib/agent/providers/geminiProvider.js | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/agent/Agent.js b/src/lib/agent/Agent.js index b3f1c6d..65f4d9e 100644 --- a/src/lib/agent/Agent.js +++ b/src/lib/agent/Agent.js @@ -2,7 +2,6 @@ const { typeRealistically } = require('../../util/realisticTyping'); const vscode = require('vscode'); const { getProvider } = require('./providers/providerInstance'); const { speak } = require('../../util/speak'); -const config = require('../../../config'); class Agent { constructor() { @@ -38,8 +37,11 @@ class Agent { this.isStreaming = true; this.provider .queryStream(prompt, (response) => this.consumeStream(response)) - .then(() => { + .then((out) => { this.isStreaming = false; + if (out.blocked) { + vscode.window.showErrorMessage(`Prompt blocked: ${out.blockReason}`); + } }); } diff --git a/src/lib/agent/providers/geminiProvider.js b/src/lib/agent/providers/geminiProvider.js index cb5cf75..e7af1fc 100644 --- a/src/lib/agent/providers/geminiProvider.js +++ b/src/lib/agent/providers/geminiProvider.js @@ -2,7 +2,6 @@ const ModelProvider = require('./genericProvider'); const { prompts } = require('../../../prompt'); const config = require('../../../../config'); const { GoogleGenerativeAI } = require('@google/generative-ai'); -const vscode = require('vscode'); class GeminiProvider extends ModelProvider { constructor() { @@ -51,9 +50,10 @@ class GeminiProvider extends ModelProvider { if (chunk.promptFeedback?.blockReason) { // prompt blocked this.messageHistory.pop(); - return vscode.window.showErrorMessage( - `Prompt blocked due to ${chunk.promptFeedback.blockReason}` - ); + return { + blocked: true, + blockReason: chunk.promptFeedback.blockReason, + }; } const text = chunk.text(); fullResponse += text; From 9ab68c767ad450011748b1125651434f79494603 Mon Sep 17 00:00:00 2001 From: Dipam Sen Date: Wed, 3 Apr 2024 01:06:43 +0530 Subject: [PATCH 3/9] return success --- src/lib/agent/providers/geminiProvider.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/agent/providers/geminiProvider.js b/src/lib/agent/providers/geminiProvider.js index e7af1fc..a76a110 100644 --- a/src/lib/agent/providers/geminiProvider.js +++ b/src/lib/agent/providers/geminiProvider.js @@ -61,6 +61,9 @@ class GeminiProvider extends ModelProvider { } await process({ response: '', event: 'done' }); this.messageHistory.push({ role: 'assistant', content: fullResponse }); + return { + success: true, + }; } } From 9032f2246230e6d7835ae2d357322d61444db3aa Mon Sep 17 00:00:00 2001 From: Supercrafter100 <58982133+supercrafter100@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:45:17 +0200 Subject: [PATCH 4/9] allow browser to send errors to the extension for it to handle them --- package-lock.json | 170 ++++++++++++++++++++++++++++++++++- package.json | 6 ++ src/commands/ignoreErrors.js | 20 +++++ src/extension.js | 7 +- src/lib/agent/Agent.js | 58 +++++++++++- src/lib/web/webserver.js | 48 ++++++++++ 6 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 src/commands/ignoreErrors.js create mode 100644 src/lib/web/webserver.js diff --git a/package-lock.json b/package-lock.json index a6cc587..16abf4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "play-sound": "^1.1.6", "replicate": "^0.29.1", "say": "^0.16.0", + "socket.io": "^4.7.5", "wavefile": "^11.0.0" }, "devDependencies": { @@ -374,6 +375,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -383,6 +389,19 @@ "node": ">= 6" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -488,6 +507,18 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -696,6 +727,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1049,12 +1088,32 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1081,7 +1140,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1200,6 +1258,34 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", @@ -2539,6 +2625,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-abi": { "version": "3.56.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz", @@ -2622,6 +2716,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ollama": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.0.tgz", @@ -3464,6 +3566,44 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/streamx": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", @@ -3810,6 +3950,14 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/wavefile": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", @@ -3968,6 +4116,26 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index cf8f857..a9cc880 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,11 @@ "command": "bizarro-devin.manualPrompt", "title": "Manually Prompt AI Agent", "category": "Bizarro Devin" + }, + { + "command": "bizarro-devin.ignoreErrors", + "title": "Enable error ignoring for the AI Agent", + "category": "Bizarro Devin" } ], "menus": { @@ -101,6 +106,7 @@ "play-sound": "^1.1.6", "replicate": "^0.29.1", "say": "^0.16.0", + "socket.io": "^4.7.5", "wavefile": "^11.0.0" } } diff --git a/src/commands/ignoreErrors.js b/src/commands/ignoreErrors.js new file mode 100644 index 0000000..c6dd5ed --- /dev/null +++ b/src/commands/ignoreErrors.js @@ -0,0 +1,20 @@ +const { getAgent } = require('../lib/agent/Agent'); +const Command = require('../lib/command'); +const vscode = require('vscode'); + +class IgnoreErrorsCommand extends Command { + constructor() { + super('bizarro-devin.ignoreErrors'); + } + + async run() { + const agent = getAgent(); + agent.ignoreErrors = !agent.ignoreErrors; + + vscode.window.showInformationMessage( + 'Error ignoring is now ' + (agent.ignoreErrors ? 'enabled' : 'disabled') + ); + } +} + +module.exports = IgnoreErrorsCommand; diff --git a/src/extension.js b/src/extension.js index 22d26ad..5926a8d 100644 --- a/src/extension.js +++ b/src/extension.js @@ -1,5 +1,7 @@ -const CommandLoader = require('./lib/commandLoader'); +const { getWebserver } = require('./lib/web/webserver'); +const CommandLoader = require('./lib/commandLoader'); +require('./lib/web/webserver'); // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -11,6 +13,9 @@ function activate(context) { const commandLoader = new CommandLoader(context); commandLoader.load('commands'); + + const webserver = getWebserver(); + webserver.start(); } function deactivate() {} diff --git a/src/lib/agent/Agent.js b/src/lib/agent/Agent.js index 65f4d9e..51d7856 100644 --- a/src/lib/agent/Agent.js +++ b/src/lib/agent/Agent.js @@ -15,7 +15,12 @@ class Agent { this.isNewPrompt = false; this.promptingTemplate = 'Dan says: {prompt}\nCurrent code in the editor:\n```\n{currentCode}\n```'; + this.errorPromptingTemplate = + 'It appears like you have made some errors in the code. Please fix them. The errors are as follows:\n{errors}\nCurrent code in the editor:\n```\n{currentCode}\n```'; this.isStreaming = false; + + this.receivedErrorList = []; + this.ignoreErrors = false; } /** @@ -28,17 +33,23 @@ class Agent { 'Please wait for the current prompt to finish processing before sending another one.' ); } - const editor = vscode.window.activeTextEditor; - this.isNewPrompt = true; + const editor = vscode.window.activeTextEditor; const prompt = this.promptingTemplate .replace('{prompt}', input) .replace('{currentCode}', editor.document.getText()); + + this.processPrompt(prompt); + } + + processPrompt(prompt) { + this.isNewPrompt = true; this.isStreaming = true; this.provider .queryStream(prompt, (response) => this.consumeStream(response)) .then((out) => { this.isStreaming = false; + this.receivedErrorList = []; // Reset the error list if (out.blocked) { vscode.window.showErrorMessage(`Prompt blocked: ${out.blockReason}`); } @@ -194,6 +205,49 @@ class Agent { await speak(step.content); } } + + async receiveBrowserMessage(message) { + // If the AI is still streaming a response, we want to ignore all incoming messages. + // This is because we cannot be certain the code is in a 'finished' state as it can still be writing code. + if (this.isStreaming || this.actionsQueue.length > 0 || this.ignoreErrors) { + return; + } + + // Add formatted message to the error list + if (message.type === 'error') { + this.receivedErrorList.push( + `${message.data.msg} at line ${message.data.lineNumber} and column ${message.data.columnNo}` + ); + } + + console.log('Received error!'); + // Schedule a timeout if there isn't one, replace the existing timeout if there is one + if (this.errorTimeout) { + console.log('Cleared previous timeout'); + clearTimeout(this.errorTimeout); + } + + this.errorTimeout = setTimeout(() => { + this.triggerErrorResponse(); + }, 1000); + console.log('Set new timeout with id ' + this.errorTimeout); + } + + async triggerErrorResponse() { + // Supply a prompt to the AI to respond to the errors + const prompt = this.errorPromptingTemplate + .replace( + '{errors}', + this.receivedErrorList.map((error) => '- ' + error).join('\n') + ) + .replace( + '{currentCode}', + vscode.window.activeTextEditor.document.getText() + ); + console.log('Triggering error response!', prompt); + + this.processPrompt(prompt); + } } // Singleton instance of the agent diff --git a/src/lib/web/webserver.js b/src/lib/web/webserver.js new file mode 100644 index 0000000..7f8bbd7 --- /dev/null +++ b/src/lib/web/webserver.js @@ -0,0 +1,48 @@ +const { getAgent } = require('../agent/Agent'); +const { Server } = require('socket.io'); + +class SocketServer { + constructor() { + this.io = new Server({ + cors: { + origin: '*', + }, + }); + } + + start() { + this.io.listen(4025); + this.io.on('connect', (socket) => { + // When socket connects, register event listener + socket.on('message', (message) => this.handleIncomingMessage(message)); + }); + console.log('Socket server started'); + } + + handleIncomingMessage(message) { + const agent = getAgent(); + agent.receiveBrowserMessage(message); + } + + broadcastReload() { + this.io.emit('reload'); + } +} + +// Singleton instance of the webserver +let webserver = null; + +/** + * Get the agent instance + * @returns {SocketServer} The agent instance + */ +const getWebserver = () => { + if (!webserver) { + webserver = new SocketServer(); + } + return webserver; +}; + +module.exports = { + getWebserver, +}; From efdd9635e581828d7b1d36725eb6c09fba74184a Mon Sep 17 00:00:00 2001 From: Supercrafter100 <58982133+supercrafter100@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:48:36 +0200 Subject: [PATCH 5/9] include index.html changes and errorCatcher.js file --- src/commands/setupFiles.js | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/commands/setupFiles.js b/src/commands/setupFiles.js index 160e515..317a2de 100644 --- a/src/commands/setupFiles.js +++ b/src/commands/setupFiles.js @@ -24,6 +24,12 @@ class SetupFilesCommand extends Command { Document + + @@ -31,6 +37,47 @@ class SetupFilesCommand extends Command { ` ); } + + async createErrorCatcherFile() { + await createFile( + 'errorCatcher.js', + `const socket = io('http://127.0.0.1:4025'); + + // Capture all errors, credits to https://github.com/processing/p5.js-web-editor/blob/develop/client%2Futils%2FpreviewEntry.js#L65 + window.onerror = async function (msg, source, lineNumber, columnNo, error) { + let data; + if (!error) { + data = msg; + } else { + data = \`\${error.name}: \${error.message}\`; + // Remove the host from the resolvedLineNo + const line = \` at \${lineNumber}:\${columnNo}\`; + data = data.concat(line); + } + + const errorData = { + msg, + source, + lineNumber, + columnNo, + error, + data, + }; + + await sendMessage(errorData, 'error'); + return false; + }; + + // Send error to backend + async function sendMessage(data, type) { + socket.emit('message', { + type, + data, + }); + } + ` + ); + } } module.exports = SetupFilesCommand; From 3ed8bd93346c36bdd0dbd7b9987c1c63cc4ae9b9 Mon Sep 17 00:00:00 2001 From: Supercrafter100 <58982133+supercrafter100@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:49:08 +0200 Subject: [PATCH 6/9] forgot this --- src/commands/setupFiles.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/setupFiles.js b/src/commands/setupFiles.js index 317a2de..3f44ff3 100644 --- a/src/commands/setupFiles.js +++ b/src/commands/setupFiles.js @@ -10,6 +10,7 @@ class SetupFilesCommand extends Command { // create index.html await this.createIndexHtml(); // create sketch.js && opening it + await this.createErrorCatcherFile(); const doc = await createFile('sketch.js'); await doc.open(); } From 0c81a1f865b013f68fee462fdec1421fd2ea0aac Mon Sep 17 00:00:00 2001 From: Supercrafter100 <58982133+supercrafter100@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:53:42 +0200 Subject: [PATCH 7/9] reload page when queue is empty and streaming is finished --- src/lib/agent/Agent.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/agent/Agent.js b/src/lib/agent/Agent.js index 51d7856..6a55cc9 100644 --- a/src/lib/agent/Agent.js +++ b/src/lib/agent/Agent.js @@ -1,3 +1,4 @@ +const { getWebserver } = require('../web/webserver'); const { typeRealistically } = require('../../util/realisticTyping'); const vscode = require('vscode'); const { getProvider } = require('./providers/providerInstance'); @@ -21,6 +22,7 @@ class Agent { this.receivedErrorList = []; this.ignoreErrors = false; + this.webserver = getWebserver(); } /** @@ -175,6 +177,9 @@ class Agent { await this.processAction(step); } this.processingQueue = false; + if (!this.isStreaming) { + this.webserver.broadcastReload(); // Trigger a browser reload for the errors to appear + } } async processAction(step) { From f74224514a3107a30f0bd0b793e653b9e0ef11f3 Mon Sep 17 00:00:00 2001 From: Supercrafter100 <58982133+supercrafter100@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:54:47 +0200 Subject: [PATCH 8/9] add browser reloading to errorCatcher.js --- src/commands/setupFiles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/setupFiles.js b/src/commands/setupFiles.js index 3f44ff3..7006455 100644 --- a/src/commands/setupFiles.js +++ b/src/commands/setupFiles.js @@ -76,6 +76,8 @@ class SetupFilesCommand extends Command { data, }); } + + socket.on('reload', () => window.location.reload()); ` ); } From 26da6ac7fd0ca90cfe399ad40177d95571db3080 Mon Sep 17 00:00:00 2001 From: Supercrafter100 <58982133+supercrafter100@users.noreply.github.com> Date: Tue, 2 Apr 2024 22:09:51 +0200 Subject: [PATCH 9/9] fix circular dependency --- src/extension.js | 3 --- src/lib/agent/Agent.js | 6 ++++-- src/lib/web/webserver.js | 23 ++++------------------- 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/extension.js b/src/extension.js index 5926a8d..729f4c8 100644 --- a/src/extension.js +++ b/src/extension.js @@ -13,9 +13,6 @@ function activate(context) { const commandLoader = new CommandLoader(context); commandLoader.load('commands'); - - const webserver = getWebserver(); - webserver.start(); } function deactivate() {} diff --git a/src/lib/agent/Agent.js b/src/lib/agent/Agent.js index 6a55cc9..b7c53ed 100644 --- a/src/lib/agent/Agent.js +++ b/src/lib/agent/Agent.js @@ -1,4 +1,4 @@ -const { getWebserver } = require('../web/webserver'); +const { SocketServer } = require('../web/webserver'); const { typeRealistically } = require('../../util/realisticTyping'); const vscode = require('vscode'); const { getProvider } = require('./providers/providerInstance'); @@ -22,7 +22,9 @@ class Agent { this.receivedErrorList = []; this.ignoreErrors = false; - this.webserver = getWebserver(); + + this.webserver = new SocketServer(this); + this.webserver.start(); } /** diff --git a/src/lib/web/webserver.js b/src/lib/web/webserver.js index 7f8bbd7..fd2f03b 100644 --- a/src/lib/web/webserver.js +++ b/src/lib/web/webserver.js @@ -1,8 +1,8 @@ -const { getAgent } = require('../agent/Agent'); const { Server } = require('socket.io'); class SocketServer { - constructor() { + constructor(agent) { + this.agent = agent; this.io = new Server({ cors: { origin: '*', @@ -20,8 +20,7 @@ class SocketServer { } handleIncomingMessage(message) { - const agent = getAgent(); - agent.receiveBrowserMessage(message); + this.agent.receiveBrowserMessage(message); } broadcastReload() { @@ -29,20 +28,6 @@ class SocketServer { } } -// Singleton instance of the webserver -let webserver = null; - -/** - * Get the agent instance - * @returns {SocketServer} The agent instance - */ -const getWebserver = () => { - if (!webserver) { - webserver = new SocketServer(); - } - return webserver; -}; - module.exports = { - getWebserver, + SocketServer, };