Merge pull request #3850 from ethereum/test_gpt

call gpt service
pull/4064/head
yann300 1 year ago committed by GitHub
commit 3d9876138f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      apps/remix-ide/src/app.js
  2. 8
      apps/remix-ide/src/app/panels/layout.ts
  3. 47
      apps/remix-ide/src/app/plugins/openaigpt.tsx
  4. 303
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  5. 16
      libs/remix-ui/renderer/src/lib/renderer.tsx
  6. 17
      libs/remix-ui/terminal/src/lib/reducers/terminalReducer.ts
  7. 50
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  8. 3
      libs/remix-ui/terminal/src/lib/types/terminalTypes.ts
  9. 1
      package.json
  10. 15
      yarn.lock

@ -46,6 +46,7 @@ import {FileDecorator} from './app/plugins/file-decorator'
import {CodeFormat} from './app/plugins/code-format' import {CodeFormat} from './app/plugins/code-format'
import {SolidityUmlGen} from './app/plugins/solidity-umlgen' import {SolidityUmlGen} from './app/plugins/solidity-umlgen'
import {ContractFlattener} from './app/plugins/contractFlattener' import {ContractFlattener} from './app/plugins/contractFlattener'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -180,6 +181,9 @@ class AppComponent {
// ----------------- ContractFlattener ---------------------------- // ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener() const contractFlattener = new ContractFlattener()
// ----------------- Open AI --------------------------------------
const openaigpt = new OpenAIGpt()
// ----------------- import content service ------------------------ // ----------------- import content service ------------------------
const contentImport = new CompilerImports() const contentImport = new CompilerImports()
@ -297,7 +301,8 @@ class AppComponent {
search, search,
solidityumlgen, solidityumlgen,
contractFlattener, contractFlattener,
solidityScript solidityScript,
openaigpt
]) ])
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
@ -410,7 +415,7 @@ class AppComponent {
]) ])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder']) await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solidity-script', 'openaigpt'])
this.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => { this.appManager.on('filePanel', 'workspaceInitializationCompleted', async () => {
// for e2e tests // for e2e tests

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = { const profile: Profile = {
name: 'layout', name: 'layout',
description: 'layout', description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel'] methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal']
} }
interface panelState { interface panelState {
@ -109,6 +109,12 @@ export class Layout extends Plugin {
this.maximised[current] = true this.maximised[current] = true
} }
async maximizeTerminal() {
this.panels.terminal.minimized = false
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
async resetSidePanel () { async resetSidePanel () {
this.event.emit('resetsidepanel') this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus') const current = await this.call('sidePanel', 'currentFocus')

@ -0,0 +1,47 @@
import { Plugin } from '@remixproject/engine'
import { CreateChatCompletionResponse } from 'openai'
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'openaigpt',
displayName: 'openaigpt',
description: 'openaigpt',
methods: ['message'],
events: [],
maintainedBy: 'Remix',
}
export class OpenAIGpt extends Plugin {
constructor() {
super(profile)
}
async message(prompt): Promise<CreateChatCompletionResponse> {
this.call('layout', 'maximizeTerminal')
this.call('terminal', 'log', 'Waiting for GPT answer...')
let result
try {
result = await (
await fetch('https://openai-gpt.remixproject.org', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt }),
})
).json()
} catch (e) {
this.call('terminal', 'log', { type: 'typewritererror', value: `Unable to get a response ${e.message}` })
return
}
if (result && result.choices && result.choices.length) {
this.call('terminal', 'log', { type: 'typewritersuccess', value: result.choices[0].message.content })
} else {
this.call('terminal', 'log', { type: 'typewritersuccess', value: 'No response...' })
}
return result.data
}
}

@ -1,30 +1,30 @@
import React, {useState, useRef, useEffect, useReducer} from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import {isArray} from 'lodash' import { isArray } from 'lodash'
import Editor, {loader, Monaco} from '@monaco-editor/react' import Editor, { loader, Monaco } from '@monaco-editor/react'
import {AlertModal} from '@remix-ui/app' import { AlertModal } from '@remix-ui/app'
import {reducerActions, reducerListener, initialState} from './actions/editor' import { reducerActions, reducerListener, initialState } from './actions/editor'
import {solidityTokensProvider, solidityLanguageConfig} from './syntaxes/solidity' import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import {cairoTokensProvider, cairoLanguageConfig} from './syntaxes/cairo' import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import {zokratesTokensProvider, zokratesLanguageConfig} from './syntaxes/zokrates' import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import {moveTokenProvider, moveLanguageConfig} from './syntaxes/move' import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move'
import {monacoTypes} from '@remix-ui/editor' import { monacoTypes } from '@remix-ui/editor'
import {loadTypes} from './web-types' import { loadTypes } from './web-types'
import {retrieveNodesAtPosition} from './helpers/retrieveNodesAtPosition' import { retrieveNodesAtPosition } from './helpers/retrieveNodesAtPosition'
import {RemixHoverProvider} from './providers/hoverProvider' import { RemixHoverProvider } from './providers/hoverProvider'
import {RemixReferenceProvider} from './providers/referenceProvider' import { RemixReferenceProvider } from './providers/referenceProvider'
import {RemixCompletionProvider} from './providers/completionProvider' import { RemixCompletionProvider } from './providers/completionProvider'
import {RemixHighLightProvider} from './providers/highlightProvider' import { RemixHighLightProvider } from './providers/highlightProvider'
import {RemixDefinitionProvider} from './providers/definitionProvider' import { RemixDefinitionProvider } from './providers/definitionProvider'
import {RemixCodeActionProvider} from './providers/codeActionProvider' import { RemixCodeActionProvider } from './providers/codeActionProvider'
import './remix-ui-editor.css' import './remix-ui-editor.css'
import {circomLanguageConfig, circomTokensProvider} from './syntaxes/circom' import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom'
import {IPosition} from 'monaco-editor' import { IPosition } from 'monaco-editor'
enum MarkerSeverity { enum MarkerSeverity {
Hint = 1, Hint = 1,
Info = 2, Info = 2,
Warning = 4, Warning = 4,
Error = 8 Error = 8,
} }
type sourceAnnotation = { type sourceAnnotation = {
@ -86,7 +86,7 @@ type errorMarker = {
file: string file: string
} }
loader.config({paths: {vs: 'assets/js/monaco-editor/min/vs'}}) loader.config({ paths: { vs: 'assets/js/monaco-editor/min/vs' } })
export type DecorationsReturn = { export type DecorationsReturn = {
currentDecorations: Array<string> currentDecorations: Array<string>
@ -108,7 +108,8 @@ export type EditorAPIType = {
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
addErrorMarker: (errors: errorMarker[], from: string) => void addErrorMarker: (errors: errorMarker[], from: string) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}, from: string) => void clearErrorMarkers: (sources: string[] | { [fileName: string]: any }, from: string) => void
getPositionAt: (offset: number) => monacoTypes.IPosition
} }
/* eslint-disable-next-line */ /* eslint-disable-next-line */
@ -152,6 +153,7 @@ export const EditorUI = (props: EditorUIProps) => {
const pasteCodeRef = useRef(false) const pasteCodeRef = useRef(false)
const editorRef = useRef(null) const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null) const monacoRef = useRef<Monaco>(null)
const currentFunction = useRef('')
const currentFileRef = useRef('') const currentFileRef = useRef('')
const currentUrlRef = useRef('') const currentUrlRef = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor // const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
@ -194,88 +196,88 @@ export const EditorUI = (props: EditorUIProps) => {
base: themeType, base: themeType,
inherit: true, // can also be false to completely replace the builtin rules inherit: true, // can also be false to completely replace the builtin rules
rules: [ rules: [
{background: darkColor.replace('#', '')}, { background: darkColor.replace('#', '') },
{foreground: textColor.replace('#', '')}, { foreground: textColor.replace('#', '') },
// global variables // global variables
{token: 'keyword.abi', foreground: blueColor}, { token: 'keyword.abi', foreground: blueColor },
{token: 'keyword.block', foreground: blueColor}, { token: 'keyword.block', foreground: blueColor },
{token: 'keyword.bytes', foreground: blueColor}, { token: 'keyword.bytes', foreground: blueColor },
{token: 'keyword.msg', foreground: blueColor}, { token: 'keyword.msg', foreground: blueColor },
{token: 'keyword.tx', foreground: blueColor}, { token: 'keyword.tx', foreground: blueColor },
// global functions // global functions
{token: 'keyword.assert', foreground: blueColor}, { token: 'keyword.assert', foreground: blueColor },
{token: 'keyword.require', foreground: blueColor}, { token: 'keyword.require', foreground: blueColor },
{token: 'keyword.revert', foreground: blueColor}, { token: 'keyword.revert', foreground: blueColor },
{token: 'keyword.blockhash', foreground: blueColor}, { token: 'keyword.blockhash', foreground: blueColor },
{token: 'keyword.keccak256', foreground: blueColor}, { token: 'keyword.keccak256', foreground: blueColor },
{token: 'keyword.sha256', foreground: blueColor}, { token: 'keyword.sha256', foreground: blueColor },
{token: 'keyword.ripemd160', foreground: blueColor}, { token: 'keyword.ripemd160', foreground: blueColor },
{token: 'keyword.ecrecover', foreground: blueColor}, { token: 'keyword.ecrecover', foreground: blueColor },
{token: 'keyword.addmod', foreground: blueColor}, { token: 'keyword.addmod', foreground: blueColor },
{token: 'keyword.mulmod', foreground: blueColor}, { token: 'keyword.mulmod', foreground: blueColor },
{token: 'keyword.selfdestruct', foreground: blueColor}, { token: 'keyword.selfdestruct', foreground: blueColor },
{token: 'keyword.type ', foreground: blueColor}, { token: 'keyword.type ', foreground: blueColor },
{token: 'keyword.gasleft', foreground: blueColor}, { token: 'keyword.gasleft', foreground: blueColor },
// specials // specials
{token: 'keyword.super', foreground: infoColor}, { token: 'keyword.super', foreground: infoColor },
{token: 'keyword.this', foreground: infoColor}, { token: 'keyword.this', foreground: infoColor },
{token: 'keyword.virtual', foreground: infoColor}, { token: 'keyword.virtual', foreground: infoColor },
// for state variables // for state variables
{token: 'keyword.constants', foreground: grayColor}, { token: 'keyword.constants', foreground: grayColor },
{token: 'keyword.override', foreground: grayColor}, { token: 'keyword.override', foreground: grayColor },
{token: 'keyword.immutable', foreground: grayColor}, { token: 'keyword.immutable', foreground: grayColor },
// data location // data location
{token: 'keyword.memory', foreground: locationColor}, { token: 'keyword.memory', foreground: locationColor },
{token: 'keyword.storage', foreground: locationColor}, { token: 'keyword.storage', foreground: locationColor },
{token: 'keyword.calldata', foreground: locationColor}, { token: 'keyword.calldata', foreground: locationColor },
// for Events // for Events
{token: 'keyword.indexed', foreground: yellowColor}, { token: 'keyword.indexed', foreground: yellowColor },
{token: 'keyword.anonymous', foreground: yellowColor}, { token: 'keyword.anonymous', foreground: yellowColor },
// for functions // for functions
{token: 'keyword.external', foreground: successColor}, { token: 'keyword.external', foreground: successColor },
{token: 'keyword.internal', foreground: successColor}, { token: 'keyword.internal', foreground: successColor },
{token: 'keyword.private', foreground: successColor}, { token: 'keyword.private', foreground: successColor },
{token: 'keyword.public', foreground: successColor}, { token: 'keyword.public', foreground: successColor },
{token: 'keyword.view', foreground: successColor}, { token: 'keyword.view', foreground: successColor },
{token: 'keyword.pure', foreground: successColor}, { token: 'keyword.pure', foreground: successColor },
{token: 'keyword.payable', foreground: successColor}, { token: 'keyword.payable', foreground: successColor },
{token: 'keyword.nonpayable', foreground: successColor}, { token: 'keyword.nonpayable', foreground: successColor },
// Errors // Errors
{token: 'keyword.Error', foreground: dangerColor}, { token: 'keyword.Error', foreground: dangerColor },
{token: 'keyword.Panic', foreground: dangerColor}, { token: 'keyword.Panic', foreground: dangerColor },
// special functions // special functions
{token: 'keyword.fallback', foreground: pinkColor}, { token: 'keyword.fallback', foreground: pinkColor },
{token: 'keyword.receive', foreground: pinkColor}, { token: 'keyword.receive', foreground: pinkColor },
{token: 'keyword.constructor', foreground: pinkColor}, { token: 'keyword.constructor', foreground: pinkColor },
// identifiers // identifiers
{token: 'keyword.identifier', foreground: warningColor}, { token: 'keyword.identifier', foreground: warningColor },
{token: 'keyword.for', foreground: warningColor}, { token: 'keyword.for', foreground: warningColor },
{token: 'keyword.break', foreground: warningColor}, { token: 'keyword.break', foreground: warningColor },
{token: 'keyword.continue', foreground: warningColor}, { token: 'keyword.continue', foreground: warningColor },
{token: 'keyword.while', foreground: warningColor}, { token: 'keyword.while', foreground: warningColor },
{token: 'keyword.do', foreground: warningColor}, { token: 'keyword.do', foreground: warningColor },
{token: 'keyword.delete', foreground: warningColor}, { token: 'keyword.delete', foreground: warningColor },
{token: 'keyword.if', foreground: yellowColor}, { token: 'keyword.if', foreground: yellowColor },
{token: 'keyword.else', foreground: yellowColor}, { token: 'keyword.else', foreground: yellowColor },
{token: 'keyword.throw', foreground: orangeColor}, { token: 'keyword.throw', foreground: orangeColor },
{token: 'keyword.catch', foreground: orangeColor}, { token: 'keyword.catch', foreground: orangeColor },
{token: 'keyword.try', foreground: orangeColor}, { token: 'keyword.try', foreground: orangeColor },
// returns // returns
{token: 'keyword.returns', foreground: greenColor}, { token: 'keyword.returns', foreground: greenColor },
{token: 'keyword.return', foreground: greenColor} { token: 'keyword.return', foreground: greenColor },
], ],
colors: { colors: {
// see https://code.visualstudio.com/api/references/theme-color for more settings // see https://code.visualstudio.com/api/references/theme-color for more settings
@ -294,8 +296,8 @@ export const EditorUI = (props: EditorUIProps) => {
'menu.background': textbackground, 'menu.background': textbackground,
'menu.selectionBackground': secondaryColor, 'menu.selectionBackground': secondaryColor,
'menu.selectionForeground': textColor, 'menu.selectionForeground': textColor,
'menu.selectionBorder': secondaryColor 'menu.selectionBorder': secondaryColor,
} },
}) })
monacoRef.current.editor.setTheme(themeName) monacoRef.current.editor.setTheme(themeName)
} }
@ -313,7 +315,7 @@ export const EditorUI = (props: EditorUIProps) => {
const file = editorModelsState[props.currentFile] const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model) editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ editorRef.current.updateOptions({
readOnly: editorModelsState[props.currentFile].readOnly readOnly: editorModelsState[props.currentFile].readOnly,
}) })
if (file.language === 'sol') { if (file.language === 'sol') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity') monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
@ -337,10 +339,10 @@ export const EditorUI = (props: EditorUIProps) => {
options: { options: {
isWholeLine: false, isWholeLine: false,
glyphMarginHoverMessage: { glyphMarginHoverMessage: {
value: (decoration.from ? `from ${decoration.from}:\n` : '') + decoration.text value: (decoration.from ? `from ${decoration.from}:\n` : '') + decoration.text,
}, },
glyphMarginClassName: `fal fa-exclamation-square text-${decoration.type === 'error' ? 'danger' : decoration.type === 'warning' ? 'warning' : 'info'}` glyphMarginClassName: `fal fa-exclamation-square text-${decoration.type === 'error' ? 'danger' : decoration.type === 'warning' ? 'warning' : 'info'}`,
} },
} }
} }
if (typeOfDecoration === 'markerPerFile') { if (typeOfDecoration === 'markerPerFile') {
@ -363,8 +365,8 @@ export const EditorUI = (props: EditorUIProps) => {
), ),
options: { options: {
isWholeLine, isWholeLine,
inlineClassName: `${isWholeLine ? 'alert-info' : 'inline-class'} border-0 highlightLine${decoration.position.start.line + 1}` inlineClassName: `${isWholeLine ? 'alert-info' : 'inline-class'} border-0 highlightLine${decoration.position.start.line + 1}`,
} },
} }
} }
if (typeOfDecoration === 'lineTextPerFile') { if (typeOfDecoration === 'lineTextPerFile') {
@ -380,11 +382,11 @@ export const EditorUI = (props: EditorUIProps) => {
options: { options: {
after: { after: {
content: ` ${lineTextDecoration.content}`, content: ` ${lineTextDecoration.content}`,
inlineClassName: `${lineTextDecoration.className}` inlineClassName: `${lineTextDecoration.className}`,
}, },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`, afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage: lineTextDecoration.hoverMessage hoverMessage: lineTextDecoration.hoverMessage,
} },
} }
} }
if (typeOfDecoration === 'lineTextPerFile') { if (typeOfDecoration === 'lineTextPerFile') {
@ -400,11 +402,11 @@ export const EditorUI = (props: EditorUIProps) => {
options: { options: {
after: { after: {
content: ` ${lineTextDecoration.content}`, content: ` ${lineTextDecoration.content}`,
inlineClassName: `${lineTextDecoration.className}` inlineClassName: `${lineTextDecoration.className}`,
}, },
afterContentClassName: `${lineTextDecoration.afterContentClassName}`, afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage: lineTextDecoration.hoverMessage hoverMessage: lineTextDecoration.hoverMessage,
} },
} }
} }
} }
@ -414,7 +416,7 @@ export const EditorUI = (props: EditorUIProps) => {
if (!model) if (!model)
return { return {
currentDecorations: [], currentDecorations: [],
registeredDecorations: [] registeredDecorations: [],
} }
const decorations = [] const decorations = []
const newRegisteredDecorations = [] const newRegisteredDecorations = []
@ -428,7 +430,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
return { return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations), currentDecorations: model.deltaDecorations(currentDecorations, decorations),
registeredDecorations: newRegisteredDecorations registeredDecorations: newRegisteredDecorations,
} }
} }
@ -436,7 +438,7 @@ export const EditorUI = (props: EditorUIProps) => {
const model = editorModelsState[filePath]?.model const model = editorModelsState[filePath]?.model
if (!model) if (!model)
return { return {
currentDecorations: [] currentDecorations: [],
} }
const decorations = [] const decorations = []
if (registeredDecorations) { if (registeredDecorations) {
@ -447,17 +449,17 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
return { return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations) currentDecorations: model.deltaDecorations(currentDecorations, decorations),
} }
} }
const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => { const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => {
const model = editorModelsState[filePath]?.model const model = editorModelsState[filePath]?.model
if (!model) return {currentDecorations: []} if (!model) return { currentDecorations: [] }
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration) const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
return { return {
currentDecorations: model.deltaDecorations([], [monacoDecoration]), currentDecorations: model.deltaDecorations([], [monacoDecoration]),
registeredDecorations: [{value: decoration, type: typeOfDecoration}] registeredDecorations: [{ value: decoration, type: typeOfDecoration }],
} }
} }
@ -478,7 +480,7 @@ export const EditorUI = (props: EditorUIProps) => {
const errorServerityMap = { const errorServerityMap = {
error: MarkerSeverity.Error, error: MarkerSeverity.Error,
warning: MarkerSeverity.Warning, warning: MarkerSeverity.Warning,
info: MarkerSeverity.Info info: MarkerSeverity.Info,
} }
if (model) { if (model) {
const markerData: monacoTypes.editor.IMarkerData = { const markerData: monacoTypes.editor.IMarkerData = {
@ -487,7 +489,7 @@ export const EditorUI = (props: EditorUIProps) => {
startColumn: (error.position.start && error.position.start.column) || 0, startColumn: (error.position.start && error.position.start.column) || 0,
endLineNumber: (error.position.end && error.position.end.line) || 0, endLineNumber: (error.position.end && error.position.end.line) || 0,
endColumn: (error.position.end && error.position.end.column) || 0, endColumn: (error.position.end && error.position.end.column) || 0,
message: error.message message: error.message,
} }
if (!allMarkersPerfile[filePath]) { if (!allMarkersPerfile[filePath]) {
allMarkersPerfile[filePath] = [] allMarkersPerfile[filePath] = []
@ -503,7 +505,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}, from: string) => { props.editorAPI.clearErrorMarkers = async (sources: string[] | { [fileName: string]: any }, from: string) => {
if (sources) { if (sources) {
for (const source of Array.isArray(sources) ? sources : Object.keys(sources)) { for (const source of Array.isArray(sources) ? sources : Object.keys(sources)) {
const filePath = source const filePath = source
@ -572,9 +574,9 @@ export const EditorUI = (props: EditorUIProps) => {
range: new monacoRef.current.Range(position.lineNumber, 1, position.lineNumber, 1), range: new monacoRef.current.Range(position.lineNumber, 1, position.lineNumber, 1),
options: { options: {
isWholeLine: false, isWholeLine: false,
glyphMarginClassName: 'fas fa-circle text-info' glyphMarginClassName: 'fas fa-circle text-info',
} },
} },
] ]
) )
prevState[currentFile][position.lineNumber] = decorationIds[0] prevState[currentFile][position.lineNumber] = decorationIds[0]
@ -626,7 +628,7 @@ export const EditorUI = (props: EditorUIProps) => {
</div> </div>
</div> </div>
</div> </div>
) ),
} }
props.plugin.call('notification', 'alert', modalContent) props.plugin.call('notification', 'alert', modalContent)
pasteCodeRef.current = true pasteCodeRef.current = true
@ -649,7 +651,7 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'zooming', // create a new grouping contextMenuGroupId: 'zooming', // create a new grouping
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal,
], ],
run: () => { run: () => {
editor.updateOptions({fontSize: editor.getOption(51) + 1}) editor.updateOptions({fontSize: editor.getOption(51) + 1})
@ -662,7 +664,7 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'zooming', // create a new grouping contextMenuGroupId: 'zooming', // create a new grouping
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus,
], ],
run: () => { run: () => {
editor.updateOptions({fontSize: editor.getOption(51) - 1}) editor.updateOptions({fontSize: editor.getOption(51) - 1})
@ -675,12 +677,48 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'formatting', // create a new grouping contextMenuGroupId: 'formatting', // create a new grouping
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyF monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyF,
], ],
run: async () => { run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
await props.plugin.call('codeFormatter', 'format', file) await props.plugin.call('codeFormatter', 'format', file)
} },
}
let gptGenerateDocumentationAction
const executeGptGenerateDocumentationAction = {
id: 'generateDocumentation',
label: 'Generate documentation for this function',
contextMenuOrder: 0, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [],
run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
const message = `
solidity code: ${content}
Generate the documentation for the function ${currentFunction.current} using the Doxygen style syntax
`
await props.plugin.call('openaigpt', 'message', message)
},
}
let gptExplainFunctionAction
const executegptExplainFunctionAction = {
id: 'explainFunction',
label: 'Explain this function',
contextMenuOrder: 1, // choose the order
contextMenuGroupId: 'gtp', // create a new grouping
keybindings: [],
run: async () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
const content = await props.plugin.call('fileManager', 'readFile', file)
const message = `
solidity code: ${content}
Explain the function ${currentFunction.current}
`
await props.plugin.call('openaigpt', 'message', message)
},
} }
const freeFunctionCondition = editor.createContextKey('freeFunctionCondition', false) const freeFunctionCondition = editor.createContextKey('freeFunctionCondition', false)
@ -693,10 +731,10 @@ export const EditorUI = (props: EditorUIProps) => {
precondition: 'freeFunctionCondition', precondition: 'freeFunctionCondition',
keybindings: [ keybindings: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR,
], ],
run: async () => { run: async () => {
const {nodesAtPosition} = await retrieveNodesAtPosition(props.editorAPI, props.plugin) const { nodesAtPosition } = await retrieveNodesAtPosition(props.editorAPI, props.plugin)
// find the contract and get the nodes of the contract and the base contracts and imports // find the contract and get the nodes of the contract and the base contracts and imports
if (nodesAtPosition && isArray(nodesAtPosition) && nodesAtPosition.length) { if (nodesAtPosition && isArray(nodesAtPosition) && nodesAtPosition.length) {
const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction') const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction')
@ -709,12 +747,14 @@ export const EditorUI = (props: EditorUIProps) => {
} else { } else {
props.plugin.call('notification', 'toast', 'Please go to Remix settings and activate the code editor features or wait that the current editor context is loaded.') props.plugin.call('notification', 'toast', 'Please go to Remix settings and activate the code editor features or wait that the current editor context is loaded.')
} }
} },
} }
editor.addAction(formatAction) editor.addAction(formatAction)
editor.addAction(zoomOutAction) editor.addAction(zoomOutAction)
editor.addAction(zoominAction) editor.addAction(zoominAction)
freeFunctionAction = editor.addAction(executeFreeFunctionAction) freeFunctionAction = editor.addAction(executeFreeFunctionAction)
gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction)
gptExplainFunctionAction = editor.addAction(executegptExplainFunctionAction)
// we have to add the command because the menu action isn't always available (see onContextMenuHandlerForFreeFunction) // we have to add the command because the menu action isn't always available (see onContextMenuHandlerForFreeFunction)
editor.addCommand(monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR, () => executeFreeFunctionAction.run()) editor.addCommand(monacoRef.current.KeyMod.Shift | monacoRef.current.KeyMod.Alt | monacoRef.current.KeyCode.KeyR, () => executeFreeFunctionAction.run())
@ -726,17 +766,34 @@ export const EditorUI = (props: EditorUIProps) => {
freeFunctionAction.dispose() freeFunctionAction.dispose()
freeFunctionAction = null freeFunctionAction = null
} }
if (gptGenerateDocumentationAction) {
gptGenerateDocumentationAction.dispose()
gptGenerateDocumentationAction = null
}
if (gptExplainFunctionAction) {
gptExplainFunctionAction.dispose()
gptExplainFunctionAction = null
}
const file = await props.plugin.call('fileManager', 'getCurrentFile') const file = await props.plugin.call('fileManager', 'getCurrentFile')
if (!file.endsWith('.sol')) { if (!file.endsWith('.sol')) {
freeFunctionCondition.set(false) freeFunctionCondition.set(false)
return return
} }
const {nodesAtPosition} = await retrieveNodesAtPosition(props.editorAPI, props.plugin) const { nodesAtPosition } = await retrieveNodesAtPosition(props.editorAPI, props.plugin)
const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction') const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction')
if (freeFunctionNode) { if (freeFunctionNode) {
executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM` executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM`
freeFunctionAction = editor.addAction(executeFreeFunctionAction) freeFunctionAction = editor.addAction(executeFreeFunctionAction)
} }
const functionImpl = nodesAtPosition.find((node) => node.kind === 'function')
if (functionImpl) {
currentFunction.current = functionImpl.name
executeGptGenerateDocumentationAction.label = `Generate documentation for the function "${functionImpl.name}"`
gptGenerateDocumentationAction = editor.addAction(executeGptGenerateDocumentationAction)
executegptExplainFunctionAction.label = `Explain the function "${functionImpl.name}"`
gptExplainFunctionAction = editor.addAction(executegptExplainFunctionAction)
}
freeFunctionCondition.set(!!freeFunctionNode) freeFunctionCondition.set(!!freeFunctionNode)
} }
contextmenu._onContextMenu = (...args) => { contextmenu._onContextMenu = (...args) => {
@ -757,7 +814,7 @@ export const EditorUI = (props: EditorUIProps) => {
editor.revealRange(input.options.selection) editor.revealRange(input.options.selection)
editor.setPosition({ editor.setPosition({
column: input.options.selection.startColumn, column: input.options.selection.startColumn,
lineNumber: input.options.selection.startLineNumber lineNumber: input.options.selection.startLineNumber,
}) })
} }
} catch (e) { } catch (e) {
@ -775,11 +832,11 @@ export const EditorUI = (props: EditorUIProps) => {
function handleEditorWillMount(monaco) { function handleEditorWillMount(monaco) {
monacoRef.current = monaco monacoRef.current = monaco
// Register a new language // Register a new language
monacoRef.current.languages.register({id: 'remix-solidity'}) monacoRef.current.languages.register({ id: 'remix-solidity' })
monacoRef.current.languages.register({id: 'remix-cairo'}) monacoRef.current.languages.register({ id: 'remix-cairo' })
monacoRef.current.languages.register({id: 'remix-zokrates'}) monacoRef.current.languages.register({ id: 'remix-zokrates' })
monacoRef.current.languages.register({id: 'remix-move'}) monacoRef.current.languages.register({ id: 'remix-move' })
monacoRef.current.languages.register({id: 'remix-circom'}) monacoRef.current.languages.register({ id: 'remix-circom' })
// Register a tokens provider for the language // Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any) monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
@ -817,7 +874,7 @@ export const EditorUI = (props: EditorUIProps) => {
beforeMount={handleEditorWillMount} beforeMount={handleEditorWillMount}
options={{ options={{
glyphMargin: true, glyphMargin: true,
readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly,
}} }}
defaultValue={defaultEditorValue} defaultValue={defaultEditorValue}
/> />

@ -67,6 +67,21 @@ export const Renderer = ({message, opt = {}, plugin}: RendererProps) => {
} }
} }
const askGtp = async () => {
try {
const content = await plugin.call('fileManager', 'readFile', editorOptions.errFile)
const message = `
solidity code: ${content}
error message: ${messageText}
explain why the error occurred and how to fix it.
`
await plugin.call('openaigpt', 'message', message)
} catch (err) {
console.error('unable to askGtp')
console.error(err)
}
}
return ( return (
<> <>
{messageText && !close && ( {messageText && !close && (
@ -82,6 +97,7 @@ export const Renderer = ({message, opt = {}, plugin}: RendererProps) => {
<i className="fas fa-times"></i> <i className="fas fa-times"></i>
</div> </div>
<CopyToClipboard content={messageText} className={` p-0 m-0 far fa-copy ${classList}`} direction={'top'} /> <CopyToClipboard content={messageText} className={` p-0 m-0 far fa-copy ${classList}`} direction={'top'} />
<span onClick={() => { askGtp() }}>ASK GPT</span>
</div> </div>
)} )}
</> </>

@ -1,4 +1,4 @@
import { CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN } from '../types/terminalTypes' import { CLEAR_CONSOLE, CMD_HISTORY, EMPTY_BLOCK, ERROR, HTML, INFO, KNOWN_TRANSACTION, LISTEN_ON_NETWORK, LOG, TYPEWRITERLOG, TYPEWRITERWARNING, TYPEWRITERSUCCESS, NEW_TRANSACTION, SCRIPT, UNKNOWN_TRANSACTION, WARN } from '../types/terminalTypes'
export const initialState = { export const initialState = {
journalBlocks: [ journalBlocks: [
@ -151,6 +151,21 @@ export const registerScriptRunnerReducer = (state, action) => {
...state, ...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider }) journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, style: 'text-log', provider: action.payload.provider })
} }
case TYPEWRITERLOG:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-log', provider: action.payload.provider })
}
case TYPEWRITERWARNING:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-warning', provider: action.payload.provider })
}
case TYPEWRITERSUCCESS:
return {
...state,
journalBlocks: initialState.journalBlocks.push({ message: action.payload.message, typewriter: true, style: 'text-success', provider: action.payload.provider })
}
case INFO: case INFO:
return { return {
...state, ...state,

@ -84,6 +84,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// terminal inputRef // terminal inputRef
const inputEl = useRef(null) const inputEl = useRef(null)
const messagesEndRef = useRef(null) const messagesEndRef = useRef(null)
const typeWriterIndexes = useRef([])
// terminal dragable // terminal dragable
const panelRef = useRef(null) const panelRef = useRef(null)
@ -389,6 +390,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const handleClearConsole = () => { const handleClearConsole = () => {
setClearConsole(true) setClearConsole(true)
typeWriterIndexes.current = []
dispatch({ type: 'clearconsole', payload: [] }) dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus() inputEl.current.focus()
} }
@ -723,21 +725,36 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div> </div>
) )
} else { } else {
return ( // typeWriterIndexes: we don't want to rerender using typewriter when the react component updates
<div className={classNameBlock} data-id="block" key={i}> if (x.typewriter && !typeWriterIndexes.current.includes(index)) {
<span className={x.style}>{msg ? msg.toString() : null}</span> typeWriterIndexes.current.push(index)
</div> return (
) <div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
typewrite(element, msg ? msg.toString() : null)
}} className={x.style}></span></div>
)
} else {
return (
<div className={classNameBlock} data-id="block" key={i}><span className={x.style}>{msg ? msg.toString() : null}</span></div>
)
}
} }
}) })
} else { } else {
if (typeof x.message !== 'function') { // typeWriterIndexes: we don't want to rerender using typewriter when the react component updates
if (x.typewriter && !typeWriterIndexes.current.includes(index)) {
typeWriterIndexes.current.push(index)
return ( return (
<div className={classNameBlock} data-id="block" key={index}> <div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
{' '} typewrite(element, x.message)
<span className={x.style}> {x.message}</span> }} className={x.style}></span></div>
</div>
) )
} else {
if (typeof x.message !== 'function') {
return (
<div className={classNameBlock} data-id="block" key={index}> <span className={x.style}> {x.message}</span></div>
)
}
} }
} }
})} })}
@ -778,7 +795,18 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
) )
} }
function isHtml(value) { const typewrite = (elementsRef, message) => {
(() => {
let count = 0
const id = setInterval(() => {
count++
elementsRef.innerText = message.substr(0, count)
if (message === count) clearInterval(id)
}, 5)
})()
}
function isHtml (value) {
if (!value.indexOf) return false if (!value.indexOf) return false
return value.indexOf('<div') !== -1 || value.indexOf('<span') !== -1 || value.indexOf('<p') !== -1 || value.indexOf('<label') !== -1 || value.indexOf('<b') !== -1 return value.indexOf('<div') !== -1 || value.indexOf('<span') !== -1 || value.indexOf('<p') !== -1 || value.indexOf('<label') !== -1 || value.indexOf('<b') !== -1
} }

@ -15,6 +15,9 @@ export const NEW_CALL = 'newCall'
export const HTML = 'html' export const HTML = 'html'
export const LOG = 'log' export const LOG = 'log'
export const TYPEWRITERLOG = 'typewriterlog'
export const TYPEWRITERWARNING = 'typewriterwarning'
export const TYPEWRITERSUCCESS = 'typewritersuccess'
export const INFO = 'info' export const INFO = 'info'
export const WARN = 'warn' export const WARN = 'warn'
export const ERROR = 'error' export const ERROR = 'error'

@ -178,6 +178,7 @@
"latest-version": "^5.1.0", "latest-version": "^5.1.0",
"merge": "^2.1.1", "merge": "^2.1.1",
"npm-install-version": "^6.0.2", "npm-install-version": "^6.0.2",
"openai": "^3.3.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"prettier-plugin-solidity": "^1.0.0-beta.24", "prettier-plugin-solidity": "^1.0.0-beta.24",

@ -7812,6 +7812,13 @@ axios@^0.21.1:
dependencies: dependencies:
follow-redirects "^1.14.0" follow-redirects "^1.14.0"
axios@^0.26.0:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"
axios@^1.4.0: axios@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
@ -21152,6 +21159,14 @@ open@^8.0.9, open@^8.4.0:
is-docker "^2.1.1" is-docker "^2.1.1"
is-wsl "^2.2.0" is-wsl "^2.2.0"
openai@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/openai/-/openai-3.3.0.tgz#a6408016ad0945738e1febf43f2fccca83a3f532"
integrity sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==
dependencies:
axios "^0.26.0"
form-data "^4.0.0"
opener@^1.5.1, opener@^1.5.2: opener@^1.5.1, opener@^1.5.2:
version "1.5.2" version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"

Loading…
Cancel
Save