Merge pull request #3850 from ethereum/test_gpt

call gpt service
pull/5370/head
yann300 1 year ago committed by GitHub
commit 2a5ffe4231
  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 {SolidityUmlGen} from './app/plugins/solidity-umlgen'
import {ContractFlattener} from './app/plugins/contractFlattener'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron')
@ -180,6 +181,9 @@ class AppComponent {
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
// ----------------- Open AI --------------------------------------
const openaigpt = new OpenAIGpt()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
@ -297,7 +301,8 @@ class AppComponent {
search,
solidityumlgen,
contractFlattener,
solidityScript
solidityScript,
openaigpt
])
// LAYOUT & SYSTEM VIEWS
@ -410,7 +415,7 @@ class AppComponent {
])
await this.appManager.activatePlugin(['settings'])
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 () => {
// for e2e tests

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel']
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal']
}
interface panelState {
@ -109,6 +109,12 @@ export class Layout extends Plugin {
this.maximised[current] = true
}
async maximizeTerminal() {
this.panels.terminal.minimized = false
this.event.emit('change', this.panels)
this.emit('change', this.panels)
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
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 {isArray} from 'lodash'
import Editor, {loader, Monaco} from '@monaco-editor/react'
import {AlertModal} from '@remix-ui/app'
import {reducerActions, reducerListener, initialState} from './actions/editor'
import {solidityTokensProvider, solidityLanguageConfig} from './syntaxes/solidity'
import {cairoTokensProvider, cairoLanguageConfig} from './syntaxes/cairo'
import {zokratesTokensProvider, zokratesLanguageConfig} from './syntaxes/zokrates'
import {moveTokenProvider, moveLanguageConfig} from './syntaxes/move'
import {monacoTypes} from '@remix-ui/editor'
import {loadTypes} from './web-types'
import {retrieveNodesAtPosition} from './helpers/retrieveNodesAtPosition'
import {RemixHoverProvider} from './providers/hoverProvider'
import {RemixReferenceProvider} from './providers/referenceProvider'
import {RemixCompletionProvider} from './providers/completionProvider'
import {RemixHighLightProvider} from './providers/highlightProvider'
import {RemixDefinitionProvider} from './providers/definitionProvider'
import {RemixCodeActionProvider} from './providers/codeActionProvider'
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { isArray } from 'lodash'
import Editor, { loader, Monaco } from '@monaco-editor/react'
import { AlertModal } from '@remix-ui/app'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solidity'
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move'
import { monacoTypes } from '@remix-ui/editor'
import { loadTypes } from './web-types'
import { retrieveNodesAtPosition } from './helpers/retrieveNodesAtPosition'
import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider'
import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider'
import { RemixCodeActionProvider } from './providers/codeActionProvider'
import './remix-ui-editor.css'
import {circomLanguageConfig, circomTokensProvider} from './syntaxes/circom'
import {IPosition} from 'monaco-editor'
import { circomLanguageConfig, circomTokensProvider } from './syntaxes/circom'
import { IPosition } from 'monaco-editor'
enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8
Error = 8,
}
type sourceAnnotation = {
@ -86,7 +86,7 @@ type errorMarker = {
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 = {
currentDecorations: Array<string>
@ -108,7 +108,8 @@ export type EditorAPIType = {
clearDecorationsByPlugin: (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
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 */
@ -152,6 +153,7 @@ export const EditorUI = (props: EditorUIProps) => {
const pasteCodeRef = useRef(false)
const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null)
const currentFunction = useRef('')
const currentFileRef = useRef('')
const currentUrlRef = useRef('')
// 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,
inherit: true, // can also be false to completely replace the builtin rules
rules: [
{background: darkColor.replace('#', '')},
{foreground: textColor.replace('#', '')},
{ background: darkColor.replace('#', '') },
{ foreground: textColor.replace('#', '') },
// global variables
{token: 'keyword.abi', foreground: blueColor},
{token: 'keyword.block', foreground: blueColor},
{token: 'keyword.bytes', foreground: blueColor},
{token: 'keyword.msg', foreground: blueColor},
{token: 'keyword.tx', foreground: blueColor},
{ token: 'keyword.abi', foreground: blueColor },
{ token: 'keyword.block', foreground: blueColor },
{ token: 'keyword.bytes', foreground: blueColor },
{ token: 'keyword.msg', foreground: blueColor },
{ token: 'keyword.tx', foreground: blueColor },
// global functions
{token: 'keyword.assert', foreground: blueColor},
{token: 'keyword.require', foreground: blueColor},
{token: 'keyword.revert', foreground: blueColor},
{token: 'keyword.blockhash', foreground: blueColor},
{token: 'keyword.keccak256', foreground: blueColor},
{token: 'keyword.sha256', foreground: blueColor},
{token: 'keyword.ripemd160', foreground: blueColor},
{token: 'keyword.ecrecover', foreground: blueColor},
{token: 'keyword.addmod', foreground: blueColor},
{token: 'keyword.mulmod', foreground: blueColor},
{token: 'keyword.selfdestruct', foreground: blueColor},
{token: 'keyword.type ', foreground: blueColor},
{token: 'keyword.gasleft', foreground: blueColor},
{ token: 'keyword.assert', foreground: blueColor },
{ token: 'keyword.require', foreground: blueColor },
{ token: 'keyword.revert', foreground: blueColor },
{ token: 'keyword.blockhash', foreground: blueColor },
{ token: 'keyword.keccak256', foreground: blueColor },
{ token: 'keyword.sha256', foreground: blueColor },
{ token: 'keyword.ripemd160', foreground: blueColor },
{ token: 'keyword.ecrecover', foreground: blueColor },
{ token: 'keyword.addmod', foreground: blueColor },
{ token: 'keyword.mulmod', foreground: blueColor },
{ token: 'keyword.selfdestruct', foreground: blueColor },
{ token: 'keyword.type ', foreground: blueColor },
{ token: 'keyword.gasleft', foreground: blueColor },
// specials
{token: 'keyword.super', foreground: infoColor},
{token: 'keyword.this', foreground: infoColor},
{token: 'keyword.virtual', foreground: infoColor},
{ token: 'keyword.super', foreground: infoColor },
{ token: 'keyword.this', foreground: infoColor },
{ token: 'keyword.virtual', foreground: infoColor },
// for state variables
{token: 'keyword.constants', foreground: grayColor},
{token: 'keyword.override', foreground: grayColor},
{token: 'keyword.immutable', foreground: grayColor},
{ token: 'keyword.constants', foreground: grayColor },
{ token: 'keyword.override', foreground: grayColor },
{ token: 'keyword.immutable', foreground: grayColor },
// data location
{token: 'keyword.memory', foreground: locationColor},
{token: 'keyword.storage', foreground: locationColor},
{token: 'keyword.calldata', foreground: locationColor},
{ token: 'keyword.memory', foreground: locationColor },
{ token: 'keyword.storage', foreground: locationColor },
{ token: 'keyword.calldata', foreground: locationColor },
// for Events
{token: 'keyword.indexed', foreground: yellowColor},
{token: 'keyword.anonymous', foreground: yellowColor},
{ token: 'keyword.indexed', foreground: yellowColor },
{ token: 'keyword.anonymous', foreground: yellowColor },
// for functions
{token: 'keyword.external', foreground: successColor},
{token: 'keyword.internal', foreground: successColor},
{token: 'keyword.private', foreground: successColor},
{token: 'keyword.public', foreground: successColor},
{token: 'keyword.view', foreground: successColor},
{token: 'keyword.pure', foreground: successColor},
{token: 'keyword.payable', foreground: successColor},
{token: 'keyword.nonpayable', foreground: successColor},
{ token: 'keyword.external', foreground: successColor },
{ token: 'keyword.internal', foreground: successColor },
{ token: 'keyword.private', foreground: successColor },
{ token: 'keyword.public', foreground: successColor },
{ token: 'keyword.view', foreground: successColor },
{ token: 'keyword.pure', foreground: successColor },
{ token: 'keyword.payable', foreground: successColor },
{ token: 'keyword.nonpayable', foreground: successColor },
// Errors
{token: 'keyword.Error', foreground: dangerColor},
{token: 'keyword.Panic', foreground: dangerColor},
{ token: 'keyword.Error', foreground: dangerColor },
{ token: 'keyword.Panic', foreground: dangerColor },
// special functions
{token: 'keyword.fallback', foreground: pinkColor},
{token: 'keyword.receive', foreground: pinkColor},
{token: 'keyword.constructor', foreground: pinkColor},
{ token: 'keyword.fallback', foreground: pinkColor },
{ token: 'keyword.receive', foreground: pinkColor },
{ token: 'keyword.constructor', foreground: pinkColor },
// identifiers
{token: 'keyword.identifier', foreground: warningColor},
{token: 'keyword.for', foreground: warningColor},
{token: 'keyword.break', foreground: warningColor},
{token: 'keyword.continue', foreground: warningColor},
{token: 'keyword.while', foreground: warningColor},
{token: 'keyword.do', foreground: warningColor},
{token: 'keyword.delete', foreground: warningColor},
{ token: 'keyword.identifier', foreground: warningColor },
{ token: 'keyword.for', foreground: warningColor },
{ token: 'keyword.break', foreground: warningColor },
{ token: 'keyword.continue', foreground: warningColor },
{ token: 'keyword.while', foreground: warningColor },
{ token: 'keyword.do', foreground: warningColor },
{ token: 'keyword.delete', foreground: warningColor },
{token: 'keyword.if', foreground: yellowColor},
{token: 'keyword.else', foreground: yellowColor},
{ token: 'keyword.if', foreground: yellowColor },
{ token: 'keyword.else', foreground: yellowColor },
{token: 'keyword.throw', foreground: orangeColor},
{token: 'keyword.catch', foreground: orangeColor},
{token: 'keyword.try', foreground: orangeColor},
{ token: 'keyword.throw', foreground: orangeColor },
{ token: 'keyword.catch', foreground: orangeColor },
{ token: 'keyword.try', foreground: orangeColor },
// returns
{token: 'keyword.returns', foreground: greenColor},
{token: 'keyword.return', foreground: greenColor}
{ token: 'keyword.returns', foreground: greenColor },
{ token: 'keyword.return', foreground: greenColor },
],
colors: {
// 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.selectionBackground': secondaryColor,
'menu.selectionForeground': textColor,
'menu.selectionBorder': secondaryColor
}
'menu.selectionBorder': secondaryColor,
},
})
monacoRef.current.editor.setTheme(themeName)
}
@ -313,7 +315,7 @@ export const EditorUI = (props: EditorUIProps) => {
const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model)
editorRef.current.updateOptions({
readOnly: editorModelsState[props.currentFile].readOnly
readOnly: editorModelsState[props.currentFile].readOnly,
})
if (file.language === 'sol') {
monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
@ -337,10 +339,10 @@ export const EditorUI = (props: EditorUIProps) => {
options: {
isWholeLine: false,
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') {
@ -363,8 +365,8 @@ export const EditorUI = (props: EditorUIProps) => {
),
options: {
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') {
@ -380,11 +382,11 @@ export const EditorUI = (props: EditorUIProps) => {
options: {
after: {
content: ` ${lineTextDecoration.content}`,
inlineClassName: `${lineTextDecoration.className}`
inlineClassName: `${lineTextDecoration.className}`,
},
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage: lineTextDecoration.hoverMessage
}
hoverMessage: lineTextDecoration.hoverMessage,
},
}
}
if (typeOfDecoration === 'lineTextPerFile') {
@ -400,11 +402,11 @@ export const EditorUI = (props: EditorUIProps) => {
options: {
after: {
content: ` ${lineTextDecoration.content}`,
inlineClassName: `${lineTextDecoration.className}`
inlineClassName: `${lineTextDecoration.className}`,
},
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
hoverMessage: lineTextDecoration.hoverMessage
}
hoverMessage: lineTextDecoration.hoverMessage,
},
}
}
}
@ -414,7 +416,7 @@ export const EditorUI = (props: EditorUIProps) => {
if (!model)
return {
currentDecorations: [],
registeredDecorations: []
registeredDecorations: [],
}
const decorations = []
const newRegisteredDecorations = []
@ -428,7 +430,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations),
registeredDecorations: newRegisteredDecorations
registeredDecorations: newRegisteredDecorations,
}
}
@ -436,7 +438,7 @@ export const EditorUI = (props: EditorUIProps) => {
const model = editorModelsState[filePath]?.model
if (!model)
return {
currentDecorations: []
currentDecorations: [],
}
const decorations = []
if (registeredDecorations) {
@ -447,17 +449,17 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
return {
currentDecorations: model.deltaDecorations(currentDecorations, decorations)
currentDecorations: model.deltaDecorations(currentDecorations, decorations),
}
}
const addDecoration = (decoration: sourceAnnotation | sourceMarker, filePath: string, typeOfDecoration: string) => {
const model = editorModelsState[filePath]?.model
if (!model) return {currentDecorations: []}
if (!model) return { currentDecorations: [] }
const monacoDecoration = convertToMonacoDecoration(decoration, typeOfDecoration)
return {
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 = {
error: MarkerSeverity.Error,
warning: MarkerSeverity.Warning,
info: MarkerSeverity.Info
info: MarkerSeverity.Info,
}
if (model) {
const markerData: monacoTypes.editor.IMarkerData = {
@ -487,7 +489,7 @@ export const EditorUI = (props: EditorUIProps) => {
startColumn: (error.position.start && error.position.start.column) || 0,
endLineNumber: (error.position.end && error.position.end.line) || 0,
endColumn: (error.position.end && error.position.end.column) || 0,
message: error.message
message: error.message,
}
if (!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) {
for (const source of Array.isArray(sources) ? sources : Object.keys(sources)) {
const filePath = source
@ -572,9 +574,9 @@ export const EditorUI = (props: EditorUIProps) => {
range: new monacoRef.current.Range(position.lineNumber, 1, position.lineNumber, 1),
options: {
isWholeLine: false,
glyphMarginClassName: 'fas fa-circle text-info'
}
}
glyphMarginClassName: 'fas fa-circle text-info',
},
},
]
)
prevState[currentFile][position.lineNumber] = decorationIds[0]
@ -626,7 +628,7 @@ export const EditorUI = (props: EditorUIProps) => {
</div>
</div>
</div>
)
),
}
props.plugin.call('notification', 'alert', modalContent)
pasteCodeRef.current = true
@ -649,7 +651,7 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'zooming', // create a new grouping
keybindings: [
// eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal,
],
run: () => {
editor.updateOptions({fontSize: editor.getOption(51) + 1})
@ -662,7 +664,7 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'zooming', // create a new grouping
keybindings: [
// eslint-disable-next-line no-bitwise
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus,
],
run: () => {
editor.updateOptions({fontSize: editor.getOption(51) - 1})
@ -675,12 +677,48 @@ export const EditorUI = (props: EditorUIProps) => {
contextMenuGroupId: 'formatting', // create a new grouping
keybindings: [
// 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 () => {
const file = await props.plugin.call('fileManager', 'getCurrentFile')
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)
@ -693,10 +731,10 @@ export const EditorUI = (props: EditorUIProps) => {
precondition: 'freeFunctionCondition',
keybindings: [
// 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 () => {
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
if (nodesAtPosition && isArray(nodesAtPosition) && nodesAtPosition.length) {
const freeFunctionNode = nodesAtPosition.find((node) => node.kind === 'freeFunction')
@ -709,12 +747,14 @@ export const EditorUI = (props: EditorUIProps) => {
} 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.')
}
}
},
}
editor.addAction(formatAction)
editor.addAction(zoomOutAction)
editor.addAction(zoominAction)
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)
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 = null
}
if (gptGenerateDocumentationAction) {
gptGenerateDocumentationAction.dispose()
gptGenerateDocumentationAction = null
}
if (gptExplainFunctionAction) {
gptExplainFunctionAction.dispose()
gptExplainFunctionAction = null
}
const file = await props.plugin.call('fileManager', 'getCurrentFile')
if (!file.endsWith('.sol')) {
freeFunctionCondition.set(false)
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')
if (freeFunctionNode) {
executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM`
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)
}
contextmenu._onContextMenu = (...args) => {
@ -757,7 +814,7 @@ export const EditorUI = (props: EditorUIProps) => {
editor.revealRange(input.options.selection)
editor.setPosition({
column: input.options.selection.startColumn,
lineNumber: input.options.selection.startLineNumber
lineNumber: input.options.selection.startLineNumber,
})
}
} catch (e) {
@ -775,11 +832,11 @@ export const EditorUI = (props: EditorUIProps) => {
function handleEditorWillMount(monaco) {
monacoRef.current = monaco
// Register a new language
monacoRef.current.languages.register({id: 'remix-solidity'})
monacoRef.current.languages.register({id: 'remix-cairo'})
monacoRef.current.languages.register({id: 'remix-zokrates'})
monacoRef.current.languages.register({id: 'remix-move'})
monacoRef.current.languages.register({id: 'remix-circom'})
monacoRef.current.languages.register({ id: 'remix-solidity' })
monacoRef.current.languages.register({ id: 'remix-cairo' })
monacoRef.current.languages.register({ id: 'remix-zokrates' })
monacoRef.current.languages.register({ id: 'remix-move' })
monacoRef.current.languages.register({ id: 'remix-circom' })
// Register a tokens provider for the language
monacoRef.current.languages.setMonarchTokensProvider('remix-solidity', solidityTokensProvider as any)
@ -817,7 +874,7 @@ export const EditorUI = (props: EditorUIProps) => {
beforeMount={handleEditorWillMount}
options={{
glyphMargin: true,
readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly
readOnly: (!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly,
}}
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 (
<>
{messageText && !close && (
@ -82,6 +97,7 @@ export const Renderer = ({message, opt = {}, plugin}: RendererProps) => {
<i className="fas fa-times"></i>
</div>
<CopyToClipboard content={messageText} className={` p-0 m-0 far fa-copy ${classList}`} direction={'top'} />
<span onClick={() => { askGtp() }}>ASK GPT</span>
</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 = {
journalBlocks: [
@ -151,6 +151,21 @@ export const registerScriptRunnerReducer = (state, action) => {
...state,
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:
return {
...state,

@ -84,6 +84,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
// terminal inputRef
const inputEl = useRef(null)
const messagesEndRef = useRef(null)
const typeWriterIndexes = useRef([])
// terminal dragable
const panelRef = useRef(null)
@ -389,6 +390,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
const handleClearConsole = () => {
setClearConsole(true)
typeWriterIndexes.current = []
dispatch({ type: 'clearconsole', payload: [] })
inputEl.current.focus()
}
@ -723,21 +725,36 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
</div>
)
} else {
return (
<div className={classNameBlock} data-id="block" key={i}>
<span className={x.style}>{msg ? msg.toString() : null}</span>
</div>
)
// 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 (
<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 {
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 (
<div className={classNameBlock} data-id="block" key={index}>
{' '}
<span className={x.style}> {x.message}</span>
</div>
<div className={classNameBlock} data-id="block" key={index}> <span ref={(element) => {
typewrite(element, x.message)
}} className={x.style}></span></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
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 LOG = 'log'
export const TYPEWRITERLOG = 'typewriterlog'
export const TYPEWRITERWARNING = 'typewriterwarning'
export const TYPEWRITERSUCCESS = 'typewritersuccess'
export const INFO = 'info'
export const WARN = 'warn'
export const ERROR = 'error'

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

@ -7812,6 +7812,13 @@ axios@^0.21.1:
dependencies:
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:
version "1.4.0"
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-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:
version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"

Loading…
Cancel
Save