Merge branch 'master' of https://github.com/ethereum/remix-project into git4
commit
f298ba1e50
@ -1,97 +0,0 @@ |
|||||||
import {Plugin} from '@remixproject/engine' |
|
||||||
import {SuggestionService, SuggestOptions} from './suggestion-service' |
|
||||||
import axios, {AxiosResponse} from 'axios' |
|
||||||
//@ts-ignore
|
|
||||||
const _paq = (window._paq = window._paq || []) //eslint-disable-line
|
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'copilot-suggestion', |
|
||||||
displayName: 'copilot-suggestion', |
|
||||||
description: 'Get Solidity suggestions in editor', |
|
||||||
methods: ['suggest', 'init', 'uninstall', 'status', 'isActivate', 'useRemoteService', 'discardRemoteService'], |
|
||||||
version: '0.1.0-alpha', |
|
||||||
maintainedBy: "Remix" |
|
||||||
} |
|
||||||
|
|
||||||
export class CopilotSuggestion extends Plugin { |
|
||||||
service: SuggestionService |
|
||||||
remoteService: string |
|
||||||
context: string |
|
||||||
ready: boolean |
|
||||||
constructor() { |
|
||||||
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 |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
useRemoteService(service: string) { |
|
||||||
this.remoteService = service |
|
||||||
} |
|
||||||
|
|
||||||
discardRemoteService() { |
|
||||||
this.remoteService = null |
|
||||||
} |
|
||||||
|
|
||||||
status () { |
|
||||||
return this.ready |
|
||||||
} |
|
||||||
|
|
||||||
async isActivate () { |
|
||||||
try { |
|
||||||
return await this.call('settings', 'get', 'settings/copilot/suggest/activate') |
|
||||||
} catch (e) { |
|
||||||
console.error(e) |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async suggest(content: string) { |
|
||||||
if (!await this.call('settings', 'get', 'settings/copilot/suggest/activate')) return { output: [{ generated_text: ''}]} |
|
||||||
|
|
||||||
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, |
|
||||||
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}) |
|
||||||
const parsedData = JSON.parse(data).trimStart() |
|
||||||
return {output: [{generated_text: parsedData}]} |
|
||||||
} else { |
|
||||||
return this.service.suggest(this.context ? this.context + '\n\n' + content : content, options) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async loadModeContent() { |
|
||||||
let importsContent = '' |
|
||||||
const imports = await this.call('codeParser', 'getImports') |
|
||||||
for (const imp of imports.modules) { |
|
||||||
try { |
|
||||||
importsContent += '\n\n' + (await this.call('contentImport', 'resolve', imp)).content |
|
||||||
} catch (e) { |
|
||||||
console.log(e) |
|
||||||
} |
|
||||||
} |
|
||||||
return importsContent |
|
||||||
} |
|
||||||
|
|
||||||
async init() { |
|
||||||
return this.service.init() |
|
||||||
} |
|
||||||
|
|
||||||
async uninstall() { |
|
||||||
this.service.terminate() |
|
||||||
} |
|
||||||
} |
|
@ -1,100 +0,0 @@ |
|||||||
import EventEmitter from 'events' |
|
||||||
|
|
||||||
export type SuggestOptions = { max_new_tokens: number, temperature: number, top_k: number, do_sample: boolean } |
|
||||||
|
|
||||||
export class SuggestionService { |
|
||||||
worker: Worker |
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
responses: { [key: number]: Function } |
|
||||||
events: EventEmitter |
|
||||||
current: number |
|
||||||
constructor() { |
|
||||||
this.worker = new Worker(new URL('./worker.js', import.meta.url), { |
|
||||||
type: 'module' |
|
||||||
}); |
|
||||||
this.events = new EventEmitter() |
|
||||||
this.responses = {} |
|
||||||
this.current |
|
||||||
} |
|
||||||
|
|
||||||
terminate(): void { |
|
||||||
this.worker.terminate() |
|
||||||
this.worker = new Worker(new URL('./worker.js', import.meta.url), { |
|
||||||
type: 'module' |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async init() { |
|
||||||
const onMessageReceived = (e) => { |
|
||||||
switch (e.data.status) { |
|
||||||
case 'initiate': |
|
||||||
this.events.emit(e.data.status, e.data) |
|
||||||
// Model file start load: add a new progress item to the list.
|
|
||||||
break; |
|
||||||
|
|
||||||
case 'progress': |
|
||||||
this.events.emit(e.data.status, e.data) |
|
||||||
// Model file progress: update one of the progress items.
|
|
||||||
break; |
|
||||||
|
|
||||||
case 'done': |
|
||||||
this.events.emit(e.data.status, e.data) |
|
||||||
// Model file loaded: remove the progress item from the list.
|
|
||||||
break; |
|
||||||
|
|
||||||
case 'ready': |
|
||||||
this.events.emit(e.data.status, e.data) |
|
||||||
// Pipeline ready: the worker is ready to accept messages.
|
|
||||||
break; |
|
||||||
|
|
||||||
case 'update': |
|
||||||
this.events.emit(e.data.status, e.data) |
|
||||||
// Generation update: update the output text.
|
|
||||||
break; |
|
||||||
|
|
||||||
case 'complete': |
|
||||||
if (this.responses[e.data.id]) { |
|
||||||
if (this.current === e.data.id) { |
|
||||||
this.responses[e.data.id](null, e.data) |
|
||||||
} else { |
|
||||||
this.responses[e.data.id]('aborted') |
|
||||||
} |
|
||||||
delete this.responses[e.data.id] |
|
||||||
this.current = null |
|
||||||
} |
|
||||||
|
|
||||||
// Generation complete: re-enable the "Generate" button
|
|
||||||
break; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
// Attach the callback function as an event listener.
|
|
||||||
this.worker.addEventListener('message', onMessageReceived) |
|
||||||
|
|
||||||
this.worker.postMessage({ |
|
||||||
cmd: 'init', |
|
||||||
model: 'Pipper/finetuned_sol' |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
suggest (content: string, options: SuggestOptions) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
if (this.current) return reject(new Error('already running')) |
|
||||||
const timespan = Date.now() |
|
||||||
this.current = timespan |
|
||||||
this.worker.postMessage({ |
|
||||||
id: timespan, |
|
||||||
cmd: 'suggest', |
|
||||||
text: content, |
|
||||||
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) |
|
||||||
resolve(result) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
|
|
||||||
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. |
|
||||||
*/ |
|
||||||
class CodeCompletionPipeline { |
|
||||||
static task = 'text-generation'; |
|
||||||
static model = null |
|
||||||
static instance = null; |
|
||||||
|
|
||||||
static async getInstance(progress_callback = null) { |
|
||||||
if (this.instance === null) { |
|
||||||
this.instance = pipeline(this.task, this.model, { progress_callback }); |
|
||||||
} |
|
||||||
|
|
||||||
return this.instance; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Listen for messages from the main thread
|
|
||||||
self.addEventListener('message', async (event) => { |
|
||||||
const { |
|
||||||
id, model, text, max_new_tokens, cmd, |
|
||||||
|
|
||||||
// Generation parameters
|
|
||||||
temperature, |
|
||||||
top_k, |
|
||||||
do_sample, |
|
||||||
} = event.data; |
|
||||||
|
|
||||||
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); |
|
||||||
}); |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if (!CodeCompletionPipeline.instance) { |
|
||||||
// Send the output back to the main thread
|
|
||||||
self.postMessage({ |
|
||||||
id, |
|
||||||
status: 'error', |
|
||||||
message: 'model not yet loaded' |
|
||||||
});
|
|
||||||
} |
|
||||||
|
|
||||||
if (cmd === 'suggest') { |
|
||||||
// Retrieve the code-completion pipeline. When called for the first time,
|
|
||||||
// this will load the pipeline and save it for future use.
|
|
||||||
let generator = await CodeCompletionPipeline.getInstance(x => { |
|
||||||
// We also add a progress callback to the pipeline so that we can
|
|
||||||
// track model loading.
|
|
||||||
self.postMessage(x); |
|
||||||
}); |
|
||||||
|
|
||||||
// Actually perform the code-completion
|
|
||||||
let output = await generator(text, { |
|
||||||
max_new_tokens, |
|
||||||
temperature, |
|
||||||
top_k, |
|
||||||
do_sample, |
|
||||||
|
|
||||||
// Allows for partial output
|
|
||||||
callback_function: x => { |
|
||||||
/*self.postMessage({ |
|
||||||
id, |
|
||||||
status: 'update', |
|
||||||
output: generator.tokenizer.decode(x[0].output_token_ids, { skip_special_tokens: true }) |
|
||||||
}); |
|
||||||
*/ |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
// Send the output back to the main thread
|
|
||||||
self.postMessage({ |
|
||||||
id, |
|
||||||
status: 'complete', |
|
||||||
output: output, |
|
||||||
});
|
|
||||||
} |
|
||||||
}); |
|
@ -0,0 +1,168 @@ |
|||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
|
||||||
|
export type SuggestOptions = { |
||||||
|
max_new_tokens: number,
|
||||||
|
temperature: number, |
||||||
|
do_sample:boolean |
||||||
|
top_k: number, |
||||||
|
top_p:number,
|
||||||
|
stream_result:boolean |
||||||
|
} |
||||||
|
|
||||||
|
const _paq = (window._paq = window._paq || []) |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'solcoder', |
||||||
|
displayName: 'solcoder', |
||||||
|
description: 'solcoder', |
||||||
|
methods: ['code_generation', 'code_completion', "solidity_answer", "code_explaining"], |
||||||
|
events: [], |
||||||
|
maintainedBy: 'Remix', |
||||||
|
} |
||||||
|
|
||||||
|
export class SolCoder extends Plugin { |
||||||
|
api_url: string |
||||||
|
completion_url: string |
||||||
|
constructor() { |
||||||
|
super(profile) |
||||||
|
this.api_url = "https://solcoder.remixproject.org" |
||||||
|
this.completion_url = "https://completion.remixproject.org" |
||||||
|
} |
||||||
|
|
||||||
|
async code_generation(prompt): Promise<any> { |
||||||
|
this.emit("aiInfering") |
||||||
|
this.call('layout', 'maximizeTerminal') |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: 'Code Generation: Waiting for Solcoder answer...'}) |
||||||
|
let result |
||||||
|
try { |
||||||
|
result = await( |
||||||
|
await fetch(this.api_url, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
Accept: 'application/json', |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({"data":[prompt, "code_completion", "", false,1000,0.9,0.92,50]}), |
||||||
|
}) |
||||||
|
).json() |
||||||
|
console.log(result) |
||||||
|
if ("error" in result){ |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result.error })
|
||||||
|
return result |
||||||
|
} |
||||||
|
return result.data |
||||||
|
} 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<any> { |
||||||
|
this.emit("aiInfering") |
||||||
|
this.call('layout', 'maximizeTerminal') |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: 'Waiting for Solcoder answer...'}) |
||||||
|
let result |
||||||
|
try { |
||||||
|
result = await( |
||||||
|
await fetch(this.api_url, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
Accept: 'application/json', |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({"data":[prompt, "solidity_answer", 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 |
||||||
|
}finally { |
||||||
|
this.emit("aiInferingDone") |
||||||
|
} |
||||||
|
if (result) { |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result.data[0]}) |
||||||
|
} else if (result.error) { |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "Error on request" }) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async code_explaining(prompt): Promise<any> { |
||||||
|
this.emit("aiInfering") |
||||||
|
this.call('layout', 'maximizeTerminal') |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: 'Explain Code: Waiting for Solcoder answer...'}) |
||||||
|
let result |
||||||
|
try { |
||||||
|
result = await( |
||||||
|
await fetch(this.api_url, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
Accept: 'application/json', |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({"data":[prompt, "code_explaining", false,2000,0.9,0.8,50]}), |
||||||
|
}) |
||||||
|
).json() |
||||||
|
if (result) { |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', 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<any> { |
||||||
|
this.emit("aiInfering") |
||||||
|
let result |
||||||
|
try { |
||||||
|
result = await( |
||||||
|
await fetch(this.completion_url, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
Accept: 'application/json', |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({"data": !options? [ |
||||||
|
prompt, // string in 'context_code' Textbox component
|
||||||
|
"code_completion", |
||||||
|
"", // string in 'comment' Textbox component
|
||||||
|
false, // boolean in 'stream_result' Checkbox component
|
||||||
|
30, // 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, |
||||||
|
"code_completion", |
||||||
|
"", |
||||||
|
options.stream_result, |
||||||
|
options.max_new_tokens, |
||||||
|
options.temperature, |
||||||
|
options.top_p, |
||||||
|
options.top_k |
||||||
|
]}), |
||||||
|
}) |
||||||
|
).json() |
||||||
|
|
||||||
|
if ("error" in result){ |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result.error })
|
||||||
|
return result |
||||||
|
} |
||||||
|
return result.data |
||||||
|
|
||||||
|
} catch (e) { |
||||||
|
this.call('terminal', 'log', { type: 'aitypewriterwarning', value: `Unable to get a response ${e.message}` }) |
||||||
|
return |
||||||
|
} finally { |
||||||
|
this.emit("aiInferingDone") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -1,7 +1,12 @@ |
|||||||
{ |
{ |
||||||
"remixUiTabs.tooltipText1": "Run script (CTRL + SHIFT + S)", |
"remixUiTabs.tooltipText1": "Run script (CTRL + SHIFT + S)", |
||||||
"remixUiTabs.tooltipText2": "Compile CTRL + S", |
"remixUiTabs.tooltipText2": "Compile CTRL + S", |
||||||
"remixUiTabs.tooltipText3": "Select .sol, .vy or .yul file to compile or a .ts or .js file and run it", |
"remixUiTabs.tooltipText3": "Select .sol or .yul file to compile or a .ts or .js file and run it", |
||||||
|
"remixUiTabs.tooltipText4": "Select .sol file to use AI tools [BETA]", |
||||||
|
"remixUiTabs.tooltipText5": "Explain the contract(s) in current file [BETA]", |
||||||
|
"remixUiTabs.tooltipText6": "Enable AI Copilot [BETA]", |
||||||
|
"remixUiTabs.tooltipText7": "Disable AI Copilot [BETA]", |
||||||
|
"remixUiTabs.tooltipText8": "AI Documentation [BETA]", |
||||||
"remixUiTabs.zoomOut": "Zoom out", |
"remixUiTabs.zoomOut": "Zoom out", |
||||||
"remixUiTabs.zoomIn": "Zoom in" |
"remixUiTabs.zoomIn": "Zoom in" |
||||||
} |
} |
||||||
|
@ -0,0 +1,31 @@ |
|||||||
|
export class CompletionTimer { |
||||||
|
private duration: number; |
||||||
|
private timerId: NodeJS.Timeout | null = null; |
||||||
|
private callback: () => void; |
||||||
|
|
||||||
|
constructor(duration: number, callback: () => void) { |
||||||
|
this.duration = duration; |
||||||
|
this.callback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
if (this.timerId) { |
||||||
|
console.error("Timer is already running."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.timerId = setTimeout(() => { |
||||||
|
this.callback(); |
||||||
|
this.timerId = null; |
||||||
|
}, this.duration); |
||||||
|
} |
||||||
|
|
||||||
|
stop() { |
||||||
|
if (this.timerId) { |
||||||
|
clearTimeout(this.timerId); |
||||||
|
this.timerId = null; |
||||||
|
} else { |
||||||
|
console.error("Timer is not running."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue