From 6d9d46342e8092d696378f700ef1aa6b84f145bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Fri, 2 Feb 2024 16:53:36 +0100 Subject: [PATCH 01/59] initial integration --- apps/remix-ide/src/app.js | 5 +++ .../suggestion-service/copilot-suggestion.ts | 14 ++++++- .../suggestion-service/suggestion-service.ts | 2 +- .../src/app/plugins/solcode_completion.tsx | 41 +++++++++++++++++++ .../src/app/tabs/locales/en/settings.json | 4 +- .../lib/providers/inlineCompletionProvider.ts | 17 ++------ 6 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 apps/remix-ide/src/app/plugins/solcode_completion.tsx diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 6f01181fa5..cceb3ec091 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -59,6 +59,7 @@ import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin' import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin' import {OpenAIGpt} from './app/plugins/openaigpt' +import {SolCodeComp} from './app/plugins/solcode_completion' const isElectron = require('is-electron') @@ -233,6 +234,7 @@ class AppComponent { // ----------------- AI -------------------------------------- const openaigpt = new OpenAIGpt() + const solcodercomp = new SolCodeComp() const copilotSuggestion = new CopilotSuggestion() // ----------------- import content service ------------------------ @@ -360,6 +362,7 @@ class AppComponent { solidityScript, templates, openaigpt, + solcodercomp, copilotSuggestion ]) @@ -515,7 +518,9 @@ class AppComponent { } ) await this.appManager.activatePlugin(['solidity-script', 'openaigpt']) + await this.appManager.activatePlugin(['solcodercomp']) + await this.appManager.activatePlugin(['filePanel']) // Set workspace after initial activation diff --git a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts index ac1798b400..a7d8e5fbfa 100644 --- a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts +++ b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts @@ -8,7 +8,7 @@ const profile = { name: 'copilot-suggestion', displayName: 'copilot-suggestion', description: 'Get Solidity suggestions in editor', - methods: ['suggest', 'init', 'uninstall', 'status', 'isActivate', 'useRemoteService', 'discardRemoteService'], + methods: ['suggest', 'init', 'uninstall', 'status', 'isActivate', 'useRemoteService', 'discardRemoteService', 'useconfig'], version: '0.1.0-alpha', maintainedBy: "Remix" } @@ -18,6 +18,7 @@ export class CopilotSuggestion extends Plugin { remoteService: string context: string ready: boolean + config: { [id: string]: string } constructor() { super(profile) this.service = new SuggestionService() @@ -30,12 +31,17 @@ export class CopilotSuggestion extends Plugin { this.service.events.on('ready', (data) => { this.ready = true }) + this.config = {} } useRemoteService(service: string) { this.remoteService = service } + useconfig(config ){ + this.config = config + } + discardRemoteService() { this.remoteService = null } @@ -61,12 +67,16 @@ export class CopilotSuggestion extends Plugin { const options: SuggestOptions = { do_sample: false, top_k: 0, + top_p: 0, + stream_result: false, temperature: temperature || 0, max_new_tokens: max_new_tokens || 0 } if (this.remoteService) { - const {data} = await axios.post(this.remoteService, {context: content, max_new_words: options.max_new_tokens, temperature: options.temperature}) + let confs = {context: content, max_new_words: options.max_new_tokens, temperature: options.temperature} + // confs = {confs, ...this.config} + const {data} = await axios.post(this.remoteService, confs) const parsedData = JSON.parse(data).trimStart() return {output: [{generated_text: parsedData}]} } else { diff --git a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts index b85647cb9f..4ec2d864dd 100644 --- a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts +++ b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts @@ -1,6 +1,6 @@ import EventEmitter from 'events' -export type SuggestOptions = { max_new_tokens: number, temperature: number, top_k: number, do_sample: boolean } +export type SuggestOptions = { max_new_tokens: number, temperature: number, top_k: number, top_p:number, do_sample: boolean, stream_result:boolean} export class SuggestionService { worker: Worker diff --git a/apps/remix-ide/src/app/plugins/solcode_completion.tsx b/apps/remix-ide/src/app/plugins/solcode_completion.tsx new file mode 100644 index 0000000000..76b4bec519 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/solcode_completion.tsx @@ -0,0 +1,41 @@ +import { Plugin } from '@remixproject/engine' +import { client } from "@gradio/client" + +const _paq = (window._paq = window._paq || []) + +const profile = { + name: 'solcoder_completion', + displayName: 'solcoder_completion', + description: 'solcoder_completion', + methods: ['message'], + events: [], + maintainedBy: 'Remix', +} + +export class SolCodeComp extends Plugin { + constructor() { + super(profile) + } + + async message(prompt): Promise { + this.call('layout', 'maximizeTerminal') + this.call('terminal', 'log', 'Waiting for GPT answer...') + let result + try { + const app = await client("http://127.0.0.1:7860/", null); + const result = await app.predict("/code_completion", [ + prompt, // string in 'context_code' Textbox component + "", // string in 'comment' Textbox component + false, // boolean in 'stream_result' Checkbox component + 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component + 0.4, // number (numeric value between 0.01 and 1) in 'temperature' Slider component + 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component + 50, // number (numeric value between 1 and 200) in 'top_k' Slider component + ]); + return result + } catch (e) { + this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) + return + } + } +} diff --git a/apps/remix-ide/src/app/tabs/locales/en/settings.json b/apps/remix-ide/src/app/tabs/locales/en/settings.json index b290156eec..18806855e6 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/settings.json +++ b/apps/remix-ide/src/app/tabs/locales/en/settings.json @@ -40,5 +40,7 @@ "settings.copilot": "Solidity copilot - Alpha", "settings.copilot.activate": "Load & Activate copilot", "settings.copilot.max_new_tokens": "Maximum number of words to generate", - "settings.copilot.temperature": "Temperature" + "settings.copilot.temperature": "Temperature", + "settings.copilot.top_k": "top_k", + "settings.copilot.top_p": "top_p" } diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 7f047833e1..5de52f2b63 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -42,7 +42,8 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli const ask = split[split.length - 2].trimStart() if (split[split.length - 1].trim() === '' && ask.startsWith('///')) { // use the code generation model - const {data} = await axios.post('https://gpt-chat.remixproject.org/infer', {comment: ask.replace('///', '')}) + + const {data} = await this.props.plugin.call('solcoder_completion', 'message', ask) const parsedData = JSON.parse(data).trimStart() const item: monacoTypes.languages.InlineCompletion = { insertText: parsedData @@ -60,21 +61,9 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli if (token.isCancellationRequested) { return } - - let result - try { - result = await this.props.plugin.call('copilot-suggestion', 'suggest', word) - } catch (err) { - return - } - const generatedText = (result as any).output[0].generated_text as string - // the generated text remove a space from the context... let clean = generatedText - if (generatedText.indexOf('@custom:dev-run-script./') !== -1) { - clean = generatedText.replace('@custom:dev-run-script', '@custom:dev-run-script ') - } - clean = clean.replace(word, '') + const item: monacoTypes.languages.InlineCompletion = { insertText: clean }; From fd5cab890def170d95666b664906779e4100eb2d Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 2 Feb 2024 17:22:04 +0100 Subject: [PATCH 02/59] add gradio de --- package.json | 1 + yarn.lock | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/package.json b/package.json index 70f84d8830..f90be37d06 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "@ethereumjs/util": "^8.0.5", "@ethereumjs/vm": "^6.4.1", "@ethersphere/bee-js": "^3.2.0", + "@gradio/client": "^0.10.1", "@isomorphic-git/lightning-fs": "^4.4.1", "@microlink/react-json-view": "^1.23.0", "@openzeppelin/contracts": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 80e97b8efc..06c0741baa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2952,6 +2952,15 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@gradio/client@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@gradio/client/-/client-0.10.1.tgz#cdd90efbc0156d8e338af61031d2c88f21134f11" + integrity sha512-C3uWIWEqlpTuG3sfPw3K3+26Fkr+jXPL8U2lC1u7DlBm25rHdGMVX17o8ApW7XcFtznfaLceVtpnDPkDpQTJlw== + dependencies: + bufferutil "^4.0.7" + semiver "^1.1.0" + ws "^8.13.0" + "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" @@ -9905,6 +9914,13 @@ bufferutil@4.0.7: dependencies: node-gyp-build "^4.3.0" +bufferutil@^4.0.7: + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + dependencies: + node-gyp-build "^4.3.0" + builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -25737,6 +25753,11 @@ selfsigned@^2.1.1: dependencies: node-forge "^1" +semiver@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semiver/-/semiver-1.1.0.tgz#9c97fb02c21c7ce4fcf1b73e2c7a24324bdddd5f" + integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg== + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -29813,6 +29834,11 @@ ws@^7.3.1, ws@^7.4.5, ws@^7.4.6, ws@^7.5.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.13.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + ws@^8.4.2: version "8.9.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" From ce953750e66dc33b809549c71289abf8e2b1d324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Wed, 7 Feb 2024 09:05:14 +0100 Subject: [PATCH 03/59] added gpt-sol and inline code completion --- apps/remix-ide/src/app.js | 8 +- .../src/app/plugins/solcode_completion.tsx | 41 ---------- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 77 +++++++++++++++++++ apps/remix-ide/src/remixAppManager.js | 1 + apps/remix-ide/src/remixEngine.js | 1 + .../lib/providers/inlineCompletionProvider.ts | 5 +- .../terminal/src/lib/remix-ui-terminal.tsx | 6 +- 7 files changed, 91 insertions(+), 48 deletions(-) delete mode 100644 apps/remix-ide/src/app/plugins/solcode_completion.tsx create mode 100644 apps/remix-ide/src/app/plugins/solcoderAI.tsx diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index cceb3ec091..33e0e4d4cb 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -59,7 +59,7 @@ import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin' import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin' import {OpenAIGpt} from './app/plugins/openaigpt' -import {SolCodeComp} from './app/plugins/solcode_completion' +import {SolCoder} from './app/plugins/solcoderAI' const isElectron = require('is-electron') @@ -234,7 +234,7 @@ class AppComponent { // ----------------- AI -------------------------------------- const openaigpt = new OpenAIGpt() - const solcodercomp = new SolCodeComp() + const solcoder = new SolCoder() const copilotSuggestion = new CopilotSuggestion() // ----------------- import content service ------------------------ @@ -362,7 +362,7 @@ class AppComponent { solidityScript, templates, openaigpt, - solcodercomp, + solcoder, copilotSuggestion ]) @@ -518,7 +518,7 @@ class AppComponent { } ) await this.appManager.activatePlugin(['solidity-script', 'openaigpt']) - await this.appManager.activatePlugin(['solcodercomp']) + await this.appManager.activatePlugin(['solcoder']) diff --git a/apps/remix-ide/src/app/plugins/solcode_completion.tsx b/apps/remix-ide/src/app/plugins/solcode_completion.tsx deleted file mode 100644 index 76b4bec519..0000000000 --- a/apps/remix-ide/src/app/plugins/solcode_completion.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Plugin } from '@remixproject/engine' -import { client } from "@gradio/client" - -const _paq = (window._paq = window._paq || []) - -const profile = { - name: 'solcoder_completion', - displayName: 'solcoder_completion', - description: 'solcoder_completion', - methods: ['message'], - events: [], - maintainedBy: 'Remix', -} - -export class SolCodeComp extends Plugin { - constructor() { - super(profile) - } - - async message(prompt): Promise { - this.call('layout', 'maximizeTerminal') - this.call('terminal', 'log', 'Waiting for GPT answer...') - let result - try { - const app = await client("http://127.0.0.1:7860/", null); - const result = await app.predict("/code_completion", [ - prompt, // string in 'context_code' Textbox component - "", // string in 'comment' Textbox component - false, // boolean in 'stream_result' Checkbox component - 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component - 0.4, // number (numeric value between 0.01 and 1) in 'temperature' Slider component - 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component - 50, // number (numeric value between 1 and 200) in 'top_k' Slider component - ]); - return result - } catch (e) { - this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) - return - } - } -} diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx new file mode 100644 index 0000000000..65b7040306 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -0,0 +1,77 @@ +import { Plugin } from '@remixproject/engine' + +const _paq = (window._paq = window._paq || []) + +const profile = { + name: 'solcoder', + displayName: 'solcoder', + description: 'solcoder', + methods: ['code_generation', 'code_completion'], + events: [], + maintainedBy: 'Remix', +} + +export class SolCoder extends Plugin { + constructor() { + super(profile) + } + + async code_generation(prompt): Promise { + this.call('layout', 'maximizeTerminal') + this.call('terminal', 'log', 'Waiting for Solcoder answer...') + let result + try { + result = await( + await fetch("https://hkfll35zthu6e2-7861.proxy.runpod.net/api/code_generation", { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data":[prompt,false,1000,0.2,0.8,50]}), + }) + ).json() + } catch (e) { + this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) + return + } + if (result) { + this.call('terminal', 'log', { type: 'typewriterwarning', value: result.data[0]}) + } else if (result.error) { + this.call('terminal', 'log', { type: 'typewriterwarning', value: "Error on request" }) + } + + } + + + async code_completion(prompt): Promise { + let result + try { + result = await( + await fetch("https://hkfll35zthu6e2-7861.proxy.runpod.net/api/code_completion", { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data":[ + prompt, // string in 'context_code' Textbox component + "", // string in 'comment' Textbox component + false, // boolean in 'stream_result' Checkbox component + 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component + 0.4, // number (numeric value between 0.01 and 1) in 'temperature' Slider component + 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component + 50, // number (numeric value between 1 and 200) in 'top_k' Slider component + ]}), + }) + ).json() + + console.log('solcoder result', result.data) + return result.data + } catch (e) { + this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) + return + } + } + +} diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 19716a2f84..d55a27a560 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -77,6 +77,7 @@ let requiredModules = [ // services + layout views + system views 'contractflattener', 'solidity-script', 'openaigpt', + 'solcoder', 'home', 'doc-viewer', 'doc-gen', diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index 5c0657ce22..1e416d0066 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -26,6 +26,7 @@ export class RemixEngine extends Engine { if (name === 'compilerloader') return { queueTimeout: 60000 * 4 } if (name === 'filePanel') return { queueTimeout: 60000 * 20 } if (name === 'openaigpt') return { queueTimeout: 60000 * 2 } + if (name === 'solcoder') return { queueTimeout: 60000 * 2 } if (name === 'cookbookdev') return { queueTimeout: 60000 * 2 } return { queueTimeout: 10000 } } diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 5de52f2b63..c6e0c19515 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -43,8 +43,9 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli if (split[split.length - 1].trim() === '' && ask.startsWith('///')) { // use the code generation model - const {data} = await this.props.plugin.call('solcoder_completion', 'message', ask) - const parsedData = JSON.parse(data).trimStart() + const data = await this.props.plugin.call('solcoder', 'code_completion', word) + console.log("received solcoder data", data) + const parsedData = data[0].trimStart() //JSON.parse(data).trimStart() const item: monacoTypes.languages.InlineCompletion = { insertText: parsedData }; diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index 2f5caaf9ac..3a35adbf08 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -239,7 +239,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) await call('openaigpt', 'message', script) _paq.push(['trackEvent', 'ai', 'openai', 'askFromTerminal']) - } else { + } else if (script.trim().startsWith('gpt-sol')) { + call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) + await call('solcoder', 'code_generation', script) + _paq.push(['trackEvent', 'ai', 'solcoder', 'askFromTerminal']) + }else { await call('scriptRunner', 'execute', script) } done() From 183840f424af1586e43763f0d095b3bc2342c5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 12:29:03 +0100 Subject: [PATCH 04/59] added solidity answer and restricted model input to 1000 words --- .../suggestion-service/copilot-suggestion.ts | 36 ++++-------- .../suggestion-service/suggestion-service.ts | 7 ++- .../copilot/suggestion-service/worker.js | 16 +++--- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 55 +++++++++++++++---- .../lib/providers/inlineCompletionProvider.ts | 16 ++++-- .../settings/src/lib/remix-ui-settings.tsx | 40 +++----------- .../terminal/src/lib/remix-ui-terminal.tsx | 4 +- .../terminal/src/lib/utils/wrapScript.ts | 2 +- 8 files changed, 89 insertions(+), 87 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts index a7d8e5fbfa..708243fca8 100644 --- a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts +++ b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts @@ -8,14 +8,13 @@ const profile = { name: 'copilot-suggestion', displayName: 'copilot-suggestion', description: 'Get Solidity suggestions in editor', - methods: ['suggest', 'init', 'uninstall', 'status', 'isActivate', 'useRemoteService', 'discardRemoteService', 'useconfig'], + methods: ['suggest', 'init', 'uninstall', 'status', 'isActivate', 'discardRemoteService', 'useconfig'], version: '0.1.0-alpha', maintainedBy: "Remix" } export class CopilotSuggestion extends Plugin { service: SuggestionService - remoteService: string context: string ready: boolean config: { [id: string]: string } @@ -23,27 +22,17 @@ export class CopilotSuggestion extends Plugin { super(profile) this.service = new SuggestionService() this.context = '' - this.service.events.on('progress', (data) => { - this.emit('loading', data) - }) - this.service.events.on('done', (data) => { - }) - this.service.events.on('ready', (data) => { - this.ready = true - }) + this.ready = true // always ready for service this.config = {} } - useRemoteService(service: string) { - this.remoteService = service - } useconfig(config ){ this.config = config } discardRemoteService() { - this.remoteService = null + this.ready = false } status () { @@ -65,22 +54,19 @@ export class CopilotSuggestion extends Plugin { const max_new_tokens = await this.call('settings', 'get', 'settings/copilot/suggest/max_new_tokens') const temperature = await this.call('settings', 'get', 'settings/copilot/suggest/temperature') const options: SuggestOptions = { - do_sample: false, - top_k: 0, - top_p: 0, + top_k: 50, + top_p: 0.92, stream_result: false, - temperature: temperature || 0, + temperature: temperature || 0.9, max_new_tokens: max_new_tokens || 0 } - if (this.remoteService) { - let confs = {context: content, max_new_words: options.max_new_tokens, temperature: options.temperature} - // confs = {confs, ...this.config} - const {data} = await axios.post(this.remoteService, confs) - const parsedData = JSON.parse(data).trimStart() + if (this.ready){ + const data = await this.call('solcoder', 'code_completion', content.split(" ").slice(-1000).join(" "), options) + const parsedData = data[0].trimStart() return {output: [{generated_text: parsedData}]} - } else { - return this.service.suggest(this.context ? this.context + '\n\n' + content : content, options) + }else{ + return } } diff --git a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts index 4ec2d864dd..49323aeda6 100644 --- a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts +++ b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/suggestion-service.ts @@ -1,6 +1,10 @@ import EventEmitter from 'events' -export type SuggestOptions = { max_new_tokens: number, temperature: number, top_k: number, top_p:number, do_sample: boolean, stream_result:boolean} +export type SuggestOptions = { max_new_tokens: number, + temperature: number, + top_k: number, + top_p:number, + stream_result:boolean} export class SuggestionService { worker: Worker @@ -89,7 +93,6 @@ export class SuggestionService { max_new_tokens: options.max_new_tokens, temperature: options.temperature, top_k: options.top_k, - do_sample: options.do_sample }) this.responses[timespan] = (error, result) => { if (error) return reject(error) diff --git a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js index a5affc48b1..864c56737d 100644 --- a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js +++ b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js @@ -35,12 +35,12 @@ self.addEventListener('message', async (event) => { if (cmd === 'init') { // Retrieve the code-completion pipeline. When called for the first time, // this will load the pipeline and save it for future use. - CodeCompletionPipeline.model = model - await CodeCompletionPipeline.getInstance(x => { - // We also add a progress callback to the pipeline so that we can - // track model loading. - self.postMessage(x); - }); + // CodeCompletionPipeline.model = model + // await CodeCompletionPipeline.getInstance(x => { + // // We also add a progress callback to the pipeline so that we can + // // track model loading. + // self.postMessage(x); + // }); return } @@ -48,8 +48,8 @@ self.addEventListener('message', async (event) => { // Send the output back to the main thread self.postMessage({ id, - status: 'error', - message: 'model not yet loaded' + status: 'info', + message: 'model not longer supported' }); } diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx index 65b7040306..298dcdde36 100644 --- a/apps/remix-ide/src/app/plugins/solcoderAI.tsx +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -1,4 +1,5 @@ import { Plugin } from '@remixproject/engine' +import {SuggestOptions} from './copilot/suggestion-service/suggestion-service' const _paq = (window._paq = window._paq || []) @@ -6,7 +7,7 @@ const profile = { name: 'solcoder', displayName: 'solcoder', description: 'solcoder', - methods: ['code_generation', 'code_completion'], + methods: ['code_generation', 'code_completion', "solidity_answer"], events: [], maintainedBy: 'Remix', } @@ -31,6 +32,28 @@ export class SolCoder extends Plugin { body: JSON.stringify({"data":[prompt,false,1000,0.2,0.8,50]}), }) ).json() + return result.data[0] + } catch (e) { + this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) + return + } + } + + async solidity_answer(prompt): Promise { + this.call('layout', 'maximizeTerminal') + this.call('terminal', 'log', 'Waiting for Solcoder answer...') + let result + try { + result = await( + await fetch("https://hkfll35zthu6e2-7861.proxy.runpod.net/api/solidity_answer", { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data":[prompt,false,1000,0.9,0.8,50]}), + }) + ).json() } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) return @@ -44,7 +67,7 @@ export class SolCoder extends Plugin { } - async code_completion(prompt): Promise { + async code_completion(prompt, options:SuggestOptions=null): Promise { let result try { result = await( @@ -54,19 +77,27 @@ export class SolCoder extends Plugin { Accept: 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({"data":[ - prompt, // string in 'context_code' Textbox component - "", // string in 'comment' Textbox component - false, // boolean in 'stream_result' Checkbox component - 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component - 0.4, // number (numeric value between 0.01 and 1) in 'temperature' Slider component - 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component - 50, // number (numeric value between 1 and 200) in 'top_k' Slider component - ]}), + body: JSON.stringify({"data": !options? [ + prompt, // string in 'context_code' Textbox component + "", // string in 'comment' Textbox component + false, // boolean in 'stream_result' Checkbox component + 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component + 0.9, // number (numeric value between 0.01 and 1) in 'temperature' Slider component + 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component + 50, // number (numeric value between 1 and 200) in 'top_k' Slider component + ] : [ + prompt, + "", + options.stream_result, + options.max_new_tokens, + options.temperature, + options.top_p, + options.top_k + ] + }), }) ).json() - console.log('solcoder result', result.data) return result.data } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index c6e0c19515..4744139524 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -41,10 +41,9 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli if (split.length < 2) return const ask = split[split.length - 2].trimStart() if (split[split.length - 1].trim() === '' && ask.startsWith('///')) { - // use the code generation model - - const data = await this.props.plugin.call('solcoder', 'code_completion', word) - console.log("received solcoder data", data) + // use the code generation model, only take max 1000 word as context + const data = await this.props.plugin.call('solcoder', 'code_completion', word.split(" ").slice(-1000).join(" ")) + console.log("solcoder completion data", data) const parsedData = data[0].trimStart() //JSON.parse(data).trimStart() const item: monacoTypes.languages.InlineCompletion = { insertText: parsedData @@ -62,8 +61,17 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli if (token.isCancellationRequested) { return } + + let result + try { + result = await this.props.plugin.call('copilot-suggestion', 'suggest', word) + } catch (err) { + return + } + const generatedText = (result as any).output[0].generated_text as string let clean = generatedText + console.log('solcoder inline data:\n', clean) const item: monacoTypes.languages.InlineCompletion = { insertText: clean diff --git a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx index 34bac76242..8b0600f509 100644 --- a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx +++ b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx @@ -134,41 +134,15 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => { copilotActivate(props.config, event.target.checked, dispatch) props.plugin.call('copilot-suggestion', 'uninstall') return - } - const message =
Please wait while the copilot is downloaded. 0/100 .
- props.plugin.on('copilot-suggestion', 'loading', (data) => { - if (!copilotDownload.current) return - const loaded = ((data.loaded / data.total) * 100).toString() - const dot = loaded.match(/(.*)\./g) - copilotDownload.current.innerText = dot ? dot[0].replace('.', '') : loaded - }) - const modalActivate: AppModal = { - id: 'loadcopilotActivate', - title: 'Download Solidity copilot', - modalType: ModalTypes.default, - okLabel: 'Close', - message, - okFn: async() => { - props.plugin.off('copilot-suggestion', 'loading') - if (await props.plugin.call('copilot-suggestion', 'status')) { - copilotActivate(props.config, true, dispatch) - } else { - props.plugin.call('copilot-suggestion', 'uninstall') - copilotActivate(props.config, false, dispatch) - } - }, - hideFn: async () => { - props.plugin.off('copilot-suggestion', 'loading') - if (await props.plugin.call('copilot-suggestion', 'status')) { - copilotActivate(props.config, true, dispatch) - } else { - props.plugin.call('copilot-suggestion', 'uninstall') - copilotActivate(props.config, false, dispatch) - } - } + } + if (await props.plugin.call('copilot-suggestion', 'status')) { + copilotActivate(props.config, true, dispatch) + } else { + props.plugin.call('copilot-suggestion', 'uninstall') + copilotActivate(props.config, false, dispatch) } + props.plugin.call('copilot-suggestion', 'init') - props.plugin.call('notification', 'modal', modalActivate) } diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index 3a35adbf08..1cea4662a0 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -239,9 +239,9 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) await call('openaigpt', 'message', script) _paq.push(['trackEvent', 'ai', 'openai', 'askFromTerminal']) - } else if (script.trim().startsWith('gpt-sol')) { + } else if (script.trim().startsWith('sol-gpt')) { call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) - await call('solcoder', 'code_generation', script) + await call('solcoder', 'solidity_answer', script) _paq.push(['trackEvent', 'ai', 'solcoder', 'askFromTerminal']) }else { await call('scriptRunner', 'execute', script) diff --git a/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts b/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts index 26ded2f581..41d027b12d 100644 --- a/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts +++ b/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts @@ -1,5 +1,5 @@ export const wrapScript = (script) => { - const isKnownScript = ['remix.', 'console.', 'git', 'gpt'].some(prefix => script.trim().startsWith(prefix)) + const isKnownScript = ['remix.', 'console.', 'git', 'gpt', 'sol-gpt'].some(prefix => script.trim().startsWith(prefix)) if (isKnownScript) return script return ` try { From 201b1cff033aa1ea601880a3efaf9d50f1750b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 12:58:02 +0100 Subject: [PATCH 05/59] fixed multiple AI tool calls on single comment/completion --- .../src/lib/providers/inlineCompletionProvider.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 4744139524..ad2fdb7440 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -8,9 +8,11 @@ const result: string = '' export class RemixInLineCompletionProvider implements monacoTypes.languages.InlineCompletionsProvider { props: EditorUIProps monaco: any + running:boolean constructor(props: any, monaco: any) { this.props = props this.monaco = monaco + this.running = false } async provideInlineCompletions(model: monacoTypes.editor.ITextModel, position: monacoTypes.Position, context: monacoTypes.languages.InlineCompletionContext, token: monacoTypes.CancellationToken): Promise> { @@ -40,14 +42,16 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli const split = word.split('\n') if (split.length < 2) return const ask = split[split.length - 2].trimStart() - if (split[split.length - 1].trim() === '' && ask.startsWith('///')) { + if (split[split.length - 1].trim() === '' && ask.startsWith('///') && (!this.running)) { // use the code generation model, only take max 1000 word as context - const data = await this.props.plugin.call('solcoder', 'code_completion', word.split(" ").slice(-1000).join(" ")) + this.running = true + const data = await this.props.plugin.call('solcoder', 'code_completion', word) console.log("solcoder completion data", data) const parsedData = data[0].trimStart() //JSON.parse(data).trimStart() const item: monacoTypes.languages.InlineCompletion = { insertText: parsedData }; + this.running =false return { items: [item], enableForwardStability: true @@ -64,7 +68,10 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli let result try { - result = await this.props.plugin.call('copilot-suggestion', 'suggest', word) + if (!this.running){ + result = await this.props.plugin.call('copilot-suggestion', 'suggest', word) + this.running = true + } } catch (err) { return } @@ -76,6 +83,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli const item: monacoTypes.languages.InlineCompletion = { insertText: clean }; + this.running=false // abort if there is a signal if (token.isCancellationRequested) { From 59ea0ac9aa8541430cab48ecb1d94f1e09c13856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 17:45:35 +0100 Subject: [PATCH 06/59] disabled inline completion on comment --- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 8 +++++--- .../editor/src/lib/providers/inlineCompletionProvider.ts | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx index 298dcdde36..44fc8b43bd 100644 --- a/apps/remix-ide/src/app/plugins/solcoderAI.tsx +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -13,8 +13,10 @@ const profile = { } export class SolCoder extends Plugin { + api_url: string constructor() { super(profile) + this.api_url = "https://hkfll35zthu6e2-7861.proxy.runpod.net/api/" } async code_generation(prompt): Promise { @@ -23,7 +25,7 @@ export class SolCoder extends Plugin { let result try { result = await( - await fetch("https://hkfll35zthu6e2-7861.proxy.runpod.net/api/code_generation", { + await fetch(this.api_url.concat("code_generation"), { method: 'POST', headers: { Accept: 'application/json', @@ -45,7 +47,7 @@ export class SolCoder extends Plugin { let result try { result = await( - await fetch("https://hkfll35zthu6e2-7861.proxy.runpod.net/api/solidity_answer", { + await fetch(this.api_url.concat("solidity_answer"), { method: 'POST', headers: { Accept: 'application/json', @@ -71,7 +73,7 @@ export class SolCoder extends Plugin { let result try { result = await( - await fetch("https://hkfll35zthu6e2-7861.proxy.runpod.net/api/code_completion", { + await fetch(this.api_url.concat("code_completion"), { method: 'POST', headers: { Accept: 'application/json', diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index ad2fdb7440..263fb39a12 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -1,6 +1,7 @@ /* eslint-disable no-control-regex */ import { EditorUIProps, monacoTypes } from '@remix-ui/editor'; import axios, {AxiosResponse} from 'axios' +import { slice } from 'lodash'; const controller = new AbortController(); const { signal } = controller; const result: string = '' @@ -68,11 +69,15 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli let result try { - if (!this.running){ + if (word.split('\n').at(-1).trimStart().startsWith('//')){ + return // disable completion on comment -> current lie + }else if (!this.running){ + console.log('last line', word.split('\n').at(-1)) result = await this.props.plugin.call('copilot-suggestion', 'suggest', word) this.running = true } } catch (err) { + this.running=false return } From 1f4a10537b25a1d7fb75dfcdaa627ec73b519ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 18:02:33 +0100 Subject: [PATCH 07/59] fixed identation lint --- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx index 44fc8b43bd..8a75ad80d4 100644 --- a/apps/remix-ide/src/app/plugins/solcoderAI.tsx +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -25,15 +25,15 @@ export class SolCoder extends Plugin { let result try { result = await( - await fetch(this.api_url.concat("code_generation"), { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({"data":[prompt,false,1000,0.2,0.8,50]}), - }) - ).json() + await fetch(this.api_url.concat("code_generation"), { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data":[prompt,false,1000,0.2,0.8,50]}), + }) + ).json() return result.data[0] } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) @@ -47,15 +47,15 @@ export class SolCoder extends Plugin { let result try { result = await( - await fetch(this.api_url.concat("solidity_answer"), { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({"data":[prompt,false,1000,0.9,0.8,50]}), - }) - ).json() + await fetch(this.api_url.concat("solidity_answer"), { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data":[prompt,false,1000,0.9,0.8,50]}), + }) + ).json() } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) return @@ -73,32 +73,32 @@ export class SolCoder extends Plugin { let result try { result = await( - await fetch(this.api_url.concat("code_completion"), { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({"data": !options? [ - prompt, // string in 'context_code' Textbox component - "", // string in 'comment' Textbox component - false, // boolean in 'stream_result' Checkbox component - 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component - 0.9, // number (numeric value between 0.01 and 1) in 'temperature' Slider component - 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component - 50, // number (numeric value between 1 and 200) in 'top_k' Slider component - ] : [ - prompt, - "", - options.stream_result, - options.max_new_tokens, - options.temperature, - options.top_p, - options.top_k - ] - }), - }) - ).json() + await fetch(this.api_url.concat("code_completion"), { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data": !options? [ + prompt, // string in 'context_code' Textbox component + "", // string in 'comment' Textbox component + false, // boolean in 'stream_result' Checkbox component + 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component + 0.9, // number (numeric value between 0.01 and 1) in 'temperature' Slider component + 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component + 50, // number (numeric value between 1 and 200) in 'top_k' Slider component + ] : [ + prompt, + "", + options.stream_result, + options.max_new_tokens, + options.temperature, + options.top_p, + options.top_k + ] + }), + }) + ).json() return result.data } catch (e) { From 3ea6f6d37bc93903aea1cada3f00eff473de5b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 18:12:57 +0100 Subject: [PATCH 08/59] fixed ci idents --- .../copilot/suggestion-service/worker.js | 1 - apps/remix-ide/src/app/plugins/solcoderAI.tsx | 49 +++++++++---------- .../lib/providers/inlineCompletionProvider.ts | 2 +- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js index 864c56737d..3d1e0f5acd 100644 --- a/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js +++ b/apps/remix-ide/src/app/plugins/copilot/suggestion-service/worker.js @@ -3,7 +3,6 @@ import { pipeline, env } from '@xenova/transformers'; env.allowLocalModels = true; -const instance = null /** * This class uses the Singleton pattern to ensure that only one instance of the pipeline is loaded. */ diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx index 8a75ad80d4..45799ae784 100644 --- a/apps/remix-ide/src/app/plugins/solcoderAI.tsx +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -74,31 +74,30 @@ export class SolCoder extends Plugin { try { result = await( await fetch(this.api_url.concat("code_completion"), { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({"data": !options? [ - prompt, // string in 'context_code' Textbox component - "", // string in 'comment' Textbox component - false, // boolean in 'stream_result' Checkbox component - 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component - 0.9, // number (numeric value between 0.01 and 1) in 'temperature' Slider component - 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component - 50, // number (numeric value between 1 and 200) in 'top_k' Slider component - ] : [ - prompt, - "", - options.stream_result, - options.max_new_tokens, - options.temperature, - options.top_p, - options.top_k - ] - }), - }) - ).json() + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data": !options? [ + prompt, // string in 'context_code' Textbox component + "", // string in 'comment' Textbox component + false, // boolean in 'stream_result' Checkbox component + 200, // number (numeric value between 0 and 2000) in 'max_new_tokens' Slider component + 0.9, // number (numeric value between 0.01 and 1) in 'temperature' Slider component + 0.90, // number (numeric value between 0 and 1) in 'top_p' Slider component + 50, // number (numeric value between 1 and 200) in 'top_k' Slider component + ] : [ + prompt, + "", + options.stream_result, + options.max_new_tokens, + options.temperature, + options.top_p, + options.top_k + ]}), + }) + ).json() return result.data } catch (e) { diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 263fb39a12..392aed0670 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -82,7 +82,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli } const generatedText = (result as any).output[0].generated_text as string - let clean = generatedText + const clean = generatedText console.log('solcoder inline data:\n', clean) const item: monacoTypes.languages.InlineCompletion = { From 6c946e815215b06330cc0e5116736b338078f3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 18:21:53 +0100 Subject: [PATCH 09/59] improved completion after ( --- .../editor/src/lib/providers/inlineCompletionProvider.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 392aed0670..0d69f848e4 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -28,7 +28,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli endColumn: position.column, }); - if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.')) { + if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.') && !word.endsWith('(') && word.split('\n').at(-1).trimStart().startsWith('//')) { return; } @@ -69,10 +69,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli let result try { - if (word.split('\n').at(-1).trimStart().startsWith('//')){ - return // disable completion on comment -> current lie - }else if (!this.running){ - console.log('last line', word.split('\n').at(-1)) + if (!this.running){ result = await this.props.plugin.call('copilot-suggestion', 'suggest', word) this.running = true } From e7e8bd1a2cff236e96e4705741989b14f99b253d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Thu, 8 Feb 2024 18:42:30 +0100 Subject: [PATCH 10/59] improved completion after } --- .../editor/src/lib/providers/inlineCompletionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 0d69f848e4..6794abfdf3 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -28,7 +28,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli endColumn: position.column, }); - if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.') && !word.endsWith('(') && word.split('\n').at(-1).trimStart().startsWith('//')) { + if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.') && !word.endsWith('(') && word.endsWith('}') && word.split('\n').at(-1).trimStart().startsWith('//')) { return; } From 88f87ba8de8de68a5a5c39898c9f22852076a357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Fri, 9 Feb 2024 13:32:31 +0100 Subject: [PATCH 11/59] minor UI changes --- apps/remix-ide/src/app/tabs/locales/en/settings.json | 2 +- libs/remix-ui/terminal/src/lib/terminalWelcome.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/tabs/locales/en/settings.json b/apps/remix-ide/src/app/tabs/locales/en/settings.json index 18806855e6..5f658450d5 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/settings.json +++ b/apps/remix-ide/src/app/tabs/locales/en/settings.json @@ -38,7 +38,7 @@ "settings.projectSecret": "PROJECT SECRET", "settings.analyticsInRemix": "Analytics in Remix IDE", "settings.copilot": "Solidity copilot - Alpha", - "settings.copilot.activate": "Load & Activate copilot", + "settings.copilot.activate": "Enable copilot", "settings.copilot.max_new_tokens": "Maximum number of words to generate", "settings.copilot.temperature": "Temperature", "settings.copilot.top_k": "top_k", diff --git a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx index 24c37b400e..81406b3199 100644 --- a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx +++ b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx @@ -57,6 +57,9 @@ const TerminalWelcomeMessage = ({packageJson, storage}) => {
  • gpt <your question here> {' '}
  • +
  • + sol-gpt <your Solidity question here> {' '} +
  • . From de27714ca73c0e07b63e3f6ac5684ffc59ad54e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Mon, 12 Feb 2024 09:13:59 +0100 Subject: [PATCH 12/59] fixed key error and disable space for code completion --- .../editor/src/lib/providers/inlineCompletionProvider.ts | 8 ++++++-- libs/remix-ui/terminal/src/lib/terminalWelcome.tsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 6794abfdf3..0c5b80a274 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -27,8 +27,12 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli endLineNumber: position.lineNumber, endColumn: position.column, }); - - if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.') && !word.endsWith('(') && word.endsWith('}') && word.split('\n').at(-1).trimStart().startsWith('//')) { + + if (!word.endsWith(', ') && + !word.endsWith('\n') && + !word.endsWith(';') && + !word.endsWith('.') && + !word.endsWith('(')) { return; } diff --git a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx index 81406b3199..3531b9ede2 100644 --- a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx +++ b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx @@ -57,7 +57,7 @@ const TerminalWelcomeMessage = ({packageJson, storage}) => {
  • gpt <your question here> {' '}
  • -
  • +
  • sol-gpt <your Solidity question here> {' '}
  • From 087833a064621caa6ec4da406a8003587b039de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Mon, 12 Feb 2024 09:25:32 +0100 Subject: [PATCH 13/59] reverted space for code completion --- .../editor/src/lib/providers/inlineCompletionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 0c5b80a274..ec86a44e43 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -28,7 +28,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli endColumn: position.column, }); - if (!word.endsWith(', ') && + if (!word.endsWith(' ') && !word.endsWith('\n') && !word.endsWith(';') && !word.endsWith('.') && From 9fb0d5d4bf6be971fbf5262ca1f8f3d2b2e38901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Mon, 12 Feb 2024 14:40:29 +0100 Subject: [PATCH 14/59] fixed in comment completion and added processing spinner --- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 11 ++++++++++- .../src/lib/providers/inlineCompletionProvider.ts | 12 ++++++++++++ .../terminal/src/lib/remix-ui-terminal.tsx | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx index 45799ae784..473081c0c2 100644 --- a/apps/remix-ide/src/app/plugins/solcoderAI.tsx +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -20,6 +20,7 @@ export class SolCoder extends Plugin { } async code_generation(prompt): Promise { + this.emit("aiInfering") this.call('layout', 'maximizeTerminal') this.call('terminal', 'log', 'Waiting for Solcoder answer...') let result @@ -34,14 +35,17 @@ export class SolCoder extends Plugin { body: JSON.stringify({"data":[prompt,false,1000,0.2,0.8,50]}), }) ).json() - return result.data[0] + return result.data[0] } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) return + }finally { + this.emit("aiInferingDone") } } async solidity_answer(prompt): Promise { + this.emit("aiInfering") this.call('layout', 'maximizeTerminal') this.call('terminal', 'log', 'Waiting for Solcoder answer...') let result @@ -59,6 +63,8 @@ export class SolCoder extends Plugin { } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) return + }finally { + this.emit("aiInferingDone") } if (result) { this.call('terminal', 'log', { type: 'typewriterwarning', value: result.data[0]}) @@ -70,6 +76,7 @@ export class SolCoder extends Plugin { async code_completion(prompt, options:SuggestOptions=null): Promise { + this.emit("aiInfering") let result try { result = await( @@ -103,6 +110,8 @@ export class SolCoder extends Plugin { } catch (e) { this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) return + } finally { + this.emit("aiInferingDone") } } diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index ec86a44e43..bd81c26fc4 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -2,6 +2,8 @@ import { EditorUIProps, monacoTypes } from '@remix-ui/editor'; import axios, {AxiosResponse} from 'axios' import { slice } from 'lodash'; +const _paq = (window._paq = window._paq || []) + const controller = new AbortController(); const { signal } = controller; const result: string = '' @@ -27,6 +29,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli endLineNumber: position.lineNumber, endColumn: position.column, }); + if (!word.endsWith(' ') && !word.endsWith('\n') && @@ -65,6 +68,15 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli } catch (e) { console.error(e) } + + if (word.split('\n').at(-1).trimStart().startsWith('//') || + word.split('\n').at(-1).trimStart().startsWith('/*') || + word.split('\n').at(-1).trimStart().startsWith('*') || + word.split('\n').at(-1).trimStart().startsWith('*/') + ){ + return; // do not do completion on single and multiline comment + } + // abort if there is a signal if (token.isCancellationRequested) { diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index 1cea4662a0..4064d8961a 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -44,6 +44,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) const [toaster, setToaster] = useState(false) + const [aiLoading, setAILoading] = useState(false) const [toastProvider, setToastProvider] = useState({ show: false, fileName: '', @@ -103,6 +104,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { setIsVM(provider.startsWith('vm-')) }) + props.plugin.on('solcoder', 'aiInfering', () => { + setAILoading(true) + }) + + props.plugin.on('solcoder', 'aiInferingDone', () => { + setAILoading(false) + }) + props.onReady({ logHtml: (html) => { scriptRunnerDispatch({ @@ -649,6 +658,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { data-id="terminalInputSearch" />
    + {aiLoading &&
    + + AI Running +
    } +
    From af4c937e1ad4d752521039aa4f71ff97dbaac572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Mon, 12 Feb 2024 14:54:32 +0100 Subject: [PATCH 15/59] duplicated button --- libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 6ea63767d3..5e4b98c98b 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -203,6 +203,40 @@ export const TabsUI = (props: TabsUIProps) => { + }> props.onZoomOut()}> From 039ade2fdcaf2ba8e8d726b255fe6ad828afbdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Tue, 13 Feb 2024 09:40:54 +0100 Subject: [PATCH 16/59] edited text --- apps/remix-ide/src/app/plugins/solcoderAI.tsx | 29 ++++++++++++++++++- .../src/app/tabs/locales/en/remixUiTabs.json | 2 ++ libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 27 +++++++++-------- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/solcoderAI.tsx b/apps/remix-ide/src/app/plugins/solcoderAI.tsx index 473081c0c2..3e69576eb0 100644 --- a/apps/remix-ide/src/app/plugins/solcoderAI.tsx +++ b/apps/remix-ide/src/app/plugins/solcoderAI.tsx @@ -7,7 +7,7 @@ const profile = { name: 'solcoder', displayName: 'solcoder', description: 'solcoder', - methods: ['code_generation', 'code_completion', "solidity_answer"], + methods: ['code_generation', 'code_completion', "solidity_answer", "code_explaining"], events: [], maintainedBy: 'Remix', } @@ -74,6 +74,33 @@ export class SolCoder extends Plugin { } + async code_explaining(prompt): Promise { + this.emit("aiInfering") + this.call('layout', 'maximizeTerminal') + this.call('terminal', 'log', 'Waiting for Solcoder answer...') + let result + try { + result = await( + await fetch(this.api_url.concat("code_explaining"), { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({"data":[prompt,false,2000,0.9,0.8,50]}), + }) + ).json() + if (result) { + this.call('terminal', 'log', { type: 'typewriterwarning', value: result.data[0]}) + } + return result.data[0] + } catch (e) { + this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` }) + return + }finally { + this.emit("aiInferingDone") + } + } async code_completion(prompt, options:SuggestOptions=null): Promise { this.emit("aiInfering") diff --git a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json index 1c60e9592d..c2740abbb1 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json +++ b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json @@ -2,6 +2,8 @@ "remixUiTabs.tooltipText1": "Run script (CTRL + SHIFT + S)", "remixUiTabs.tooltipText2": "Compile CTRL + S", "remixUiTabs.tooltipText3": "Select .sol or .yul file to compile or a .ts or .js file and run it", + "remixUiTabs.tooltipText4": "Select .sol file", + "remixUiTabs.tooltipText5": "Explain the contract/s in current file", "remixUiTabs.zoomOut": "Zoom out", "remixUiTabs.zoomIn": "Zoom in" } diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 5e4b98c98b..ce4cce6ffc 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -204,19 +204,20 @@ export const TabsUI = (props: TabsUIProps) => { + }> props.onZoomOut()}> From 1a7d5c1c2cce0e90bb1a5b36bc58e1744ea93296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tetsing?= Date: Fri, 16 Feb 2024 14:24:15 +0100 Subject: [PATCH 17/59] added initial AI toggle button --- .../src/app/tabs/locales/en/remixUiTabs.json | 3 ++- apps/remix-ide/src/app/tabs/settings-tab.tsx | 13 ++++++++- .../settings/src/lib/remix-ui-settings.tsx | 1 + libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 27 +++++++++++++++++-- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json index c2740abbb1..f001d1d4f9 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json +++ b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json @@ -2,8 +2,9 @@ "remixUiTabs.tooltipText1": "Run script (CTRL + SHIFT + S)", "remixUiTabs.tooltipText2": "Compile CTRL + S", "remixUiTabs.tooltipText3": "Select .sol or .yul file to compile or a .ts or .js file and run it", - "remixUiTabs.tooltipText4": "Select .sol file", + "remixUiTabs.tooltipText4": "Select .sol file to explain with AI", "remixUiTabs.tooltipText5": "Explain the contract/s in current file", + "remixUiTabs.tooltipText6": "AI Copilot", "remixUiTabs.zoomOut": "Zoom out", "remixUiTabs.zoomIn": "Zoom in" } diff --git a/apps/remix-ide/src/app/tabs/settings-tab.tsx b/apps/remix-ide/src/app/tabs/settings-tab.tsx index 8bbf8b2c8a..b094dd8026 100644 --- a/apps/remix-ide/src/app/tabs/settings-tab.tsx +++ b/apps/remix-ide/src/app/tabs/settings-tab.tsx @@ -15,7 +15,7 @@ const _paq = (window._paq = window._paq || []) const profile = { name: 'settings', displayName: 'Settings', - methods: ['get'], + methods: ['get', 'updateCopilotChoice'], events: [], icon: 'assets/img/settings.webp', description: 'Remix-IDE settings', @@ -36,6 +36,7 @@ module.exports = class SettingsTab extends ViewPlugin { } element: HTMLDivElement public useMatomoAnalytics: any + public useCopilot: any dispatch: React.Dispatch = () => {} constructor(config, editor) { super(profile) @@ -51,6 +52,7 @@ module.exports = class SettingsTab extends ViewPlugin { this.element = document.createElement('div') this.element.setAttribute('id', 'settingsTab') this.useMatomoAnalytics = null + this.useCopilot = null } setDispatch(dispatch: React.Dispatch) { @@ -74,6 +76,7 @@ module.exports = class SettingsTab extends ViewPlugin { editor={state.editor} _deps={state._deps} useMatomoAnalytics={state.useMatomoAnalytics} + useCopilot={state.useCopilot} themeModule={state._deps.themeModule} localeModule={state._deps.localeModule} /> @@ -88,6 +91,14 @@ module.exports = class SettingsTab extends ViewPlugin { return this.config.get(key) } + updateCopilotChoice(isChecked) { + this.config.set('settings/copilot/suggest/activate', isChecked) + this.useCopilot = isChecked + this.dispatch({ + ...this + }) + } + updateMatomoAnalyticsChoice(isChecked) { this.config.set('settings/matomo-analytics', isChecked) this.useMatomoAnalytics = isChecked diff --git a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx index 8b0600f509..54c0644248 100644 --- a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx +++ b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx @@ -36,6 +36,7 @@ export interface RemixUiSettingsProps { editor: any _deps: any useMatomoAnalytics: boolean + useCopilot: boolean themeModule: ThemeModule localeModule: LocaleModule } diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index ce4cce6ffc..fb110ea411 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -62,6 +62,7 @@ export const TabsUI = (props: TabsUIProps) => { const currentIndexRef = useRef(-1) const tabsRef = useRef({}) const tabsElement = useRef(null) + const [ai_switch, setAI_switch] = useState(true) const tabs = useRef(props.tabs) tabs.current = props.tabs // we do this to pass the tabs list to the onReady callbacks @@ -206,7 +207,7 @@ export const TabsUI = (props: TabsUIProps) => { + + }> + props.onZoomOut()}> + +diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +index 2f5caaf9a..768a8c67c 100644 +--- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx ++++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +@@ -44,6 +44,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { + const [cmdHistory, cmdHistoryDispatch] = useReducer(addCommandHistoryReducer, initialState) + const [, scriptRunnerDispatch] = useReducer(registerScriptRunnerReducer, initialState) + const [toaster, setToaster] = useState(false) ++ const [aiLoading, setAILoading] = useState(false) + const [toastProvider, setToastProvider] = useState({ + show: false, + fileName: '', +@@ -103,6 +104,14 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { + setIsVM(provider.startsWith('vm-')) + }) + ++ props.plugin.on('solcoder', 'aiInfering', () => { ++ setAILoading(true) ++ }) ++ ++ props.plugin.on('solcoder', 'aiInferingDone', () => { ++ setAILoading(false) ++ }) ++ + props.onReady({ + logHtml: (html) => { + scriptRunnerDispatch({ +@@ -239,7 +248,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { + call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) + await call('openaigpt', 'message', script) + _paq.push(['trackEvent', 'ai', 'openai', 'askFromTerminal']) +- } else { ++ } else if (script.trim().startsWith('sol-gpt')) { ++ call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) ++ await call('solcoder', 'solidity_answer', script) ++ _paq.push(['trackEvent', 'ai', 'solcoder', 'askFromTerminal']) ++ }else { + await call('scriptRunner', 'execute', script) + } + done() +@@ -645,6 +658,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { + data-id="terminalInputSearch" + /> +
    ++ {aiLoading &&
    ++ ++ AI Running ... ++
    } ++ + + +
    +diff --git a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx +index 24c37b400..3531b9ede 100644 +--- a/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx ++++ b/libs/remix-ui/terminal/src/lib/terminalWelcome.tsx +@@ -57,6 +57,9 @@ const TerminalWelcomeMessage = ({packageJson, storage}) => { +
  • + gpt <your question here> {' '} +
  • ++
  • ++ sol-gpt <your Solidity question here> {' '} ++
  • + +
    + . +diff --git a/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts b/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts +index 26ded2f58..41d027b12 100644 +--- a/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts ++++ b/libs/remix-ui/terminal/src/lib/utils/wrapScript.ts +@@ -1,5 +1,5 @@ + export const wrapScript = (script) => { +- const isKnownScript = ['remix.', 'console.', 'git', 'gpt'].some(prefix => script.trim().startsWith(prefix)) ++ const isKnownScript = ['remix.', 'console.', 'git', 'gpt', 'sol-gpt'].some(prefix => script.trim().startsWith(prefix)) + if (isKnownScript) return script + return ` + try { +diff --git a/libs/remix-ui/vyper-compile-details/src/lib/vyper-compile-details.tsx b/libs/remix-ui/vyper-compile-details/src/lib/vyper-compile-details.tsx +index cbb7353fb..63da76b41 100644 +--- a/libs/remix-ui/vyper-compile-details/src/lib/vyper-compile-details.tsx ++++ b/libs/remix-ui/vyper-compile-details/src/lib/vyper-compile-details.tsx +@@ -10,13 +10,12 @@ interface RemixUiVyperCompileDetailsProps { + } + + export function RemixUiVyperCompileDetails({ payload, theme, themeStyle }: RemixUiVyperCompileDetailsProps) { +- const compileResult = payload['compileResult'] ?? {} +- console.log('compileResult', compileResult, payload) +- const bcode = compileResult.bytecode ? compileResult.bytecode.object : '' +- const runtimeBcode = compileResult.runtimeBytecode ? compileResult.runtimeBytecode.object : '' +- const ir = compileResult.ir +- const methodIdentifiers= compileResult.methodIdentifiers +- const abi= compileResult.abi ++ const dpayload = Object.values(payload) as any ?? {} ++ const bcode = dpayload[0].bytecode ? dpayload[0].bytecode.object : '' ++ const runtimeBcode = dpayload[0].runtimeBytecode ? dpayload[0].runtimeBytecode.object : '' ++ const ir = dpayload[0].ir ++ const methodIdentifiers= dpayload[0].methodIdentifiers ++ const abi= dpayload[0].abi + return ( + <> + ('abi') + const tabContent = [ + { +diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer-hovericons.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer-hovericons.tsx +deleted file mode 100644 +index c6b299f66..000000000 +--- a/libs/remix-ui/workspace/src/lib/components/file-explorer-hovericons.tsx ++++ /dev/null +@@ -1,100 +0,0 @@ +-import React, { useState } from 'react' +-import { CustomTooltip } from '@remix-ui/helper' +-import { FormattedMessage } from 'react-intl' +- +-export type FileHoverIconsProps = { +- file: any +- handleNewFolderOp?: any +- handleNewFileOp?: any +- renamePathOp?: (path: string, type: string, isNew?: boolean) => void +- deletePathOp?: (path: string | string[]) => void | Promise +-} +- +-export function FileHoverIcons(props: FileHoverIconsProps) { +- const [mouseOver, setMouseOver] = useState(false) +- return ( +- <> +- {
    +- { +- props.file.isDirectory ? ( +- <> +- } +- tooltipId={`filePanel.createNewFolder.${props.file.path}`} +- tooltipClasses="text-nowrap" +- > +- { +- e.stopPropagation() +- await props.handleNewFolderOp(props.file.path) +- }} +- style={{ cursor: mouseOver ? 'pointer' : 'default' }} +- onMouseEnter={(e) => setMouseOver(true)} +- onMouseLeave={(e) => setMouseOver(false)} +- > +- +- } +- tooltipId={`fileExplorer.createNewFile.${props.file.path}`} +- tooltipClasses="text-nowrap" +- > +- { +- e.stopPropagation() +- await props.handleNewFileOp(props.file.path) +- }} +- style={{ cursor: mouseOver ? 'pointer' : 'default' }} +- onMouseEnter={(e) => setMouseOver(true)} +- onMouseLeave={(e) => setMouseOver(false)} +- > +- +- +- ) : null +- } +- } +- tooltipId={`filePanel.rename.${props.file.path}`} +- tooltipClasses="text-nowrap" +- > +- { +- e.stopPropagation() +- props.renamePathOp(props.file.path, props.file.type) +- }} +- style={{ cursor: mouseOver ? 'pointer' : 'default' }} +- onMouseEnter={(e) => setMouseOver(true)} +- onMouseLeave={(e) => setMouseOver(false)} +- > +- +- } +- tooltipId={`filePanel.deleteItem.${props.file.path}`} +- tooltipClasses="text-nowrap" +- > +- { +- e.stopPropagation() +- await props.deletePathOp(props.file.path) +- }} +- style={{ cursor: mouseOver ? 'pointer' : 'default' }} +- onMouseEnter={(e) => setMouseOver(true)} +- onMouseLeave={(e) => setMouseOver(false)} +- > +- +-
    +- } +- +- ) +-} +diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +index 6e2010f80..39e20428d 100644 +--- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx ++++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +@@ -1,4 +1,4 @@ +-import React, { useEffect, useState, useRef, SyntheticEvent } from 'react' // eslint-disable-line ++import React, { useEffect, useState, useRef, SyntheticEvent, useTransition } from 'react' // eslint-disable-line + import { useIntl } from 'react-intl' + import { TreeView } from '@remix-ui/tree-view' // eslint-disable-line + import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line +@@ -27,13 +27,12 @@ export const FileExplorer = (props: FileExplorerProps) => { + handleContextMenu, + handleNewFileInput, + handleNewFolderInput, +- deletePath, + uploadFile, + uploadFolder, + fileState + } = props + const [state, setState] = useState(workspaceState) +- // const [isPending, startTransition] = useTransition(); ++ const [isPending, startTransition] = useTransition(); + const treeRef = useRef(null) + + useEffect(() => { +@@ -131,7 +130,6 @@ export const FileExplorer = (props: FileExplorerProps) => { + + const renamePath = async (oldPath: string, newPath: string) => { + try { +- if (oldPath === newPath) return + props.dispatchRenamePath(oldPath, newPath) + } catch (error) { + props.modal( +@@ -406,10 +404,6 @@ export const FileExplorer = (props: FileExplorerProps) => { + moveFile={handleFileMove} + moveFolder={handleFolderMove} + handleClickFolder={handleClickFolder} +- createNewFile={props.createNewFile} +- createNewFolder={props.createNewFolder} +- deletePath={deletePath} +- editPath={props.editModeOn} + /> +
    +
    +diff --git a/libs/remix-ui/workspace/src/lib/components/flat-tree-drop.tsx b/libs/remix-ui/workspace/src/lib/components/flat-tree-drop.tsx +index de274562f..e1d7fc72c 100644 +--- a/libs/remix-ui/workspace/src/lib/components/flat-tree-drop.tsx ++++ b/libs/remix-ui/workspace/src/lib/components/flat-tree-drop.tsx +@@ -1,4 +1,4 @@ +-import React, { SyntheticEvent, useEffect, useRef, useState } from 'react' ++import React, { SyntheticEvent, startTransition, useEffect, useRef, useState } from 'react' + import { FileType } from '../types' + import { getEventTarget } from '../utils/getEventTarget' + import { extractParentFromKey } from '@remix-ui/helper' +@@ -23,6 +23,7 @@ export const FlatTreeDrop = (props: FlatTreeDropProps) => { + const onDragOver = async (e: SyntheticEvent) => { + e.preventDefault() + const target = await getEventTarget(e) ++ + if (!target || !target.path) { + clearTimeout(timer) + setFolderToOpen(null) +@@ -35,7 +36,7 @@ export const FlatTreeDrop = (props: FlatTreeDropProps) => { + setFolderToOpen(null) + } + if (dragDestination && dragDestination.isDirectory && !expandPath.includes(dragDestination.path) && folderToOpen !== dragDestination.path && props.handleClickFolder) { +- ++ + setFolderToOpen(dragDestination.path) + timer && clearTimeout(timer) + setTimer( +@@ -60,6 +61,7 @@ export const FlatTreeDrop = (props: FlatTreeDropProps) => { + } else { + dragDestination = getFlatTreeItem(target.path) + } ++ + if (dragDestination.isDirectory) { + if (dragSource.isDirectory) { + moveFolder(dragDestination.path, dragSource.path) +@@ -82,4 +84,4 @@ export const FlatTreeDrop = (props: FlatTreeDropProps) => { + onDrop={onDrop} onDragOver={onDragOver} + className="d-flex h-100" + >{props.children}) +-} ++} +\ No newline at end of file +diff --git a/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx b/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx +index 4bdddffe5..f86cd2873 100644 +--- a/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx ++++ b/libs/remix-ui/workspace/src/lib/components/flat-tree.tsx +@@ -7,8 +7,6 @@ import { FlatTreeItemInput } from './flat-tree-item-input'; + import { FlatTreeDrop } from './flat-tree-drop'; + import { getEventTarget } from '../utils/getEventTarget'; + import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators'; +-import { FileHoverIcons } from './file-explorer-hovericons'; +-import { deletePath } from '../actions'; + + export default function useOnScreen(ref: RefObject) { + +@@ -38,10 +36,6 @@ interface FlatTreeProps { + moveFile: (dest: string, src: string) => void + moveFolder: (dest: string, src: string) => void + fileState: fileDecoration[] +- createNewFile?: any +- createNewFolder?: any +- deletePath?: (path: string | string[]) => void | Promise +- editPath?: (path: string, type: string, isNew?: boolean) => void + } + + let mouseTimer: any = { +@@ -50,7 +44,7 @@ let mouseTimer: any = { + } + + export const FlatTree = (props: FlatTreeProps) => { +- const { files, flatTree, expandPath, focusEdit, editModeOff, handleTreeClick, moveFile, moveFolder, fileState, focusElement, handleClickFolder, deletePath, editPath } = props ++ const { files, flatTree, expandPath, focusEdit, editModeOff, handleTreeClick, moveFile, moveFolder, fileState, focusElement, handleClickFolder } = props + const [hover, setHover] = useState('') + const [mouseOverTarget, setMouseOverTarget] = useState<{ + path: string, +@@ -184,62 +178,42 @@ export const FlatTree = (props: FlatTreeProps) => { + } + }, [focusEdit]) + +- const showIcons = (file: FileType) => +- file.path === hover && !isDragging ? ( +-
    +- +-
    +- ) : null + + const Row = (index: number) => { + const node = Object.keys(flatTree)[index] + const file = flatTree[node] +- return ( +-
  • { +- setHover(file.path) +- }} +- onMouseOut={() => { +- setHover('') +- }} +- data-type={file.isDirectory ? 'folder' : 'file'} +- data-path={`${file.path}`} +- data-id={`treeViewLitreeViewItem${file.path}`} +- > +-
    +- {getIndentLevelDiv(file.path)} ++ return (
  • setHover(file.path)} ++ onMouseOut={() => setHover(file.path)} ++ data-type={file.isDirectory ? 'folder' : 'file'} ++ data-path={`${file.path}`} ++ data-id={`treeViewLitreeViewItem${file.path}`} ++ > ++
    ++ {getIndentLevelDiv(file.path)} + +-
    +- {focusEdit && file.path && focusEdit.element === file.path ? +- : +- <>
    +- {file.name} +-
    +-
    +- {showIcons(file)} +- {getFileStateIcons(file)} +-
    +- +- } +-
    +-
  • ) ++
    ++ {focusEdit && file.path && focusEdit.element === file.path ? ++ : ++ <>
    ++ {file.name} ++ ++
    ++ {getFileStateIcons(file)} ++ ++ } ++ ++ ) + } + + return (<> +diff --git a/libs/remix-ui/workspace/src/lib/css/file-explorer.css b/libs/remix-ui/workspace/src/lib/css/file-explorer.css +index 16f0ffca5..c49a0a54a 100644 +--- a/libs/remix-ui/workspace/src/lib/css/file-explorer.css ++++ b/libs/remix-ui/workspace/src/lib/css/file-explorer.css +@@ -60,12 +60,4 @@ ul { + [contenteditable] { + -webkit-user-select: text; + user-select: text; +-} +- +-.remixui_icons { +- +-} +- +-.remixui_icons:hover { +- color: var(--text); +-} ++} +\ No newline at end of file +diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +index e71d94ffd..400aa1de2 100644 +--- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx ++++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +@@ -796,7 +796,7 @@ export function Workspace() { + + ++ + +