commit
84ccd8098b
@ -1,103 +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=false |
||||
constructor() { |
||||
super(profile) |
||||
this.context = '' |
||||
} |
||||
|
||||
onActivation(): void { |
||||
this.service = new SuggestionService() |
||||
this.service.events.on('progress', (data) => { |
||||
this.emit('loading', data) |
||||
}) |
||||
this.service.events.on('done', (data) => { |
||||
}) |
||||
this.service.events.on('ready', (data) => { |
||||
this.emit('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: true, |
||||
top_k: 50, |
||||
top_p: 0.92,
|
||||
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}) |
||||
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,107 +0,0 @@ |
||||
import EventEmitter from 'events' |
||||
|
||||
export type SuggestOptions = { |
||||
max_new_tokens: number,
|
||||
temperature: number, |
||||
do_sample:boolean |
||||
top_k: number, |
||||
top_p:number,
|
||||
stream_result: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 |
||||
} |
||||
|
||||
//todo ask Yann if we should keep the model
|
||||
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, |
||||
}) |
||||
this.responses[timespan] = (error, result) => { |
||||
if (error) return reject(error) |
||||
resolve(result) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -1,89 +0,0 @@ |
||||
|
||||
import { pipeline, env } from '@xenova/transformers'; |
||||
|
||||
env.allowLocalModels = true; |
||||
|
||||
/** |
||||
* 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,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