diff --git a/apps/circuit-compiler/src/app/components/container.tsx b/apps/circuit-compiler/src/app/components/container.tsx index 2c7cc41e16..faa12d2c7c 100644 --- a/apps/circuit-compiler/src/app/components/container.tsx +++ b/apps/circuit-compiler/src/app/components/container.tsx @@ -74,7 +74,8 @@ export function Container () { explain why the error occurred and how to fix it. ` // @ts-ignore - await circuitApp.plugin.call('remixAI', 'error_explaining', message) + await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message) + // await circuitApp.plugin.call('remixAI', 'error_explaining', message) } else { const message = ` error message: ${error} @@ -82,7 +83,8 @@ export function Container () { explain why the error occurred and how to fix it. ` // @ts-ignore - await circuitApp.plugin.call('remixAI', 'error_explaining', message) + await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message) + //await circuitApp.plugin.call('remixAI', 'error_explaining', message) } } else { const error = report.message @@ -92,7 +94,8 @@ export function Container () { explain why the error occurred and how to fix it. ` // @ts-ignore - await circuitApp.plugin.call('remixAI', 'error_explaining', message) + //await circuitApp.plugin.call('remixAI', 'error_explaining', message) + await circuitApp.plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message) } } diff --git a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx index cb1b5453aa..51983ea620 100644 --- a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx +++ b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx @@ -73,7 +73,6 @@ export class RemixAIPlugin extends ViewPlugin { } } else { - // on browser this.remoteInferencer = new RemoteInferencer(remoteModel?.apiUrl, remoteModel?.completionUrl) this.remoteInferencer.event.on('onInference', () => { this.isInferencing = true @@ -114,8 +113,6 @@ export class RemixAIPlugin extends ViewPlugin { return } - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: `\n\nWaiting for RemixAI answer...` }) - let result if (this.isOnDesktop) { result = await this.call(this.remixDesktopPluginName, 'solidity_answer', prompt) @@ -123,7 +120,6 @@ export class RemixAIPlugin extends ViewPlugin { result = await this.remoteInferencer.solidity_answer(prompt) } if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) - // this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI Done" }) return result } @@ -132,8 +128,6 @@ export class RemixAIPlugin extends ViewPlugin { this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) return } - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: `\n\nWaiting for RemixAI answer...` }) - let result if (this.isOnDesktop) { @@ -143,33 +137,22 @@ export class RemixAIPlugin extends ViewPlugin { result = await this.remoteInferencer.code_explaining(prompt, context, params) } if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) - // this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI Done" }) - - // HandleStreamResponse(result, (text) => { - // this.call('terminal', 'log', { type: 'aitypewriterwarning', value: text }) - // }) return result } - async error_explaining(prompt: string): Promise { + async error_explaining(prompt: string, context: string="", params: IParams=GenerationParams): Promise { if (this.isInferencing) { this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI is already busy!" }) return } - const params:IParams = GenerationParams - params.stream_result = true - - this.call('terminal', 'log', { type: 'aitypewriterwarning', value: `\n\nWaiting for RemixAI answer...` }) - let result if (this.isOnDesktop) { result = await this.call(this.remixDesktopPluginName, 'error_explaining', prompt) } else { result = await this.remoteInferencer.error_explaining(prompt, params) } - if (result) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) - // this.call('terminal', 'log', { type: 'aitypewriterwarning', value: "RemixAI Done" }) + if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) return result } @@ -181,20 +164,20 @@ export class RemixAIPlugin extends ViewPlugin { } } - chatPipe(fn, prompt: string, context?: string, params: IParams=GenerationParams){ + chatPipe(fn, prompt: string, context?: string, pipeMessage?: string){ if (this.chatRequestBuffer == null){ this.chatRequestBuffer = { fn_name: fn, prompt: prompt, - params: params, context: context } - - if (fn === "code_explaining"){ - ChatApi.composer.send("Explain the current code") - } - else if (fn === "solidity_answer"){ - ChatApi.composer.send("Answer the following question") + console.log('pipe message', pipeMessage) + if (pipeMessage) ChatApi.composer.send(pipeMessage) + else { + if (fn === "code_explaining") ChatApi.composer.send("Explain the current code") + else if (fn === "error_explaining") ChatApi.composer.send("Explain the error") + else if (fn === "solidity_answer") ChatApi.composer.send("Answer the following question") + else console.log("chatRequestBuffer is not empty. First process the last request.") } } else{ @@ -202,7 +185,8 @@ export class RemixAIPlugin extends ViewPlugin { } } - ProcessChatRequestBuffer(params:IParams=GenerationParams){ + + async ProcessChatRequestBuffer(params:IParams=GenerationParams){ if (this.chatRequestBuffer != null){ const result = this[this.chatRequestBuffer.fn_name](this.chatRequestBuffer.prompt, this.chatRequestBuffer.context, params) this.chatRequestBuffer = null @@ -210,14 +194,13 @@ export class RemixAIPlugin extends ViewPlugin { } else{ console.log("chatRequestBuffer is empty.") + return "" } } isChatRequestPending(){ return this.chatRequestBuffer != null } - - render() { return ( diff --git a/apps/remix-ide/src/app/tabs/locales/en/editor.json b/apps/remix-ide/src/app/tabs/locales/en/editor.json index 76bfc87a1f..6f0f367555 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/editor.json +++ b/apps/remix-ide/src/app/tabs/locales/en/editor.json @@ -25,8 +25,9 @@ "editor.explainFunction": "Explain this function", "editor.explainFunctionSol": "Explain this code", "editor.explainFunction2": "Explain the function \"{name}\"", - "editor.explainFunctionByAI": "solidity code: {content}\n Explain the function {currentFunction}", - "editor.explainFunctionByAISol": "solidity code: {content}\n Explain the function {currentFunction}", + "editor.explainFunctionByAI": "```\n{content}\n```\nExplain the function {currentFunction}", + "editor.explainFunctionByAISol": "```\n{content}\n```\nExplain the function {currentFunction}", + "editor.ExplainPipeMessage": "```\n {content}\n```\nExplain the snipped above", "editor.executeFreeFunction": "Run a free function", "editor.executeFreeFunction2": "Run the free function \"{name}\"", "editor.toastText1": "This can only execute free function", diff --git a/apps/vyper/src/app/utils/remix-client.tsx b/apps/vyper/src/app/utils/remix-client.tsx index 6d39d609d2..3034dcb27d 100644 --- a/apps/vyper/src/app/utils/remix-client.tsx +++ b/apps/vyper/src/app/utils/remix-client.tsx @@ -72,7 +72,8 @@ export class RemixClient extends PluginClient { ${message} can you explain why this error occurred and how to fix it? ` - await this.client.call('remixAI' as any, 'error_explaining', message) + // await this.client.call('remixAI' as any, 'error_explaining', message) + await this.client.call('remixAI' as any, 'chatPipe', 'error_explaining', message) } catch (err) { console.error('unable to askGpt') console.error(err) diff --git a/libs/remix-ai-core/src/helpers/streamHandler.ts b/libs/remix-ai-core/src/helpers/streamHandler.ts index 8f397f3e7f..ae13088c07 100644 --- a/libs/remix-ai-core/src/helpers/streamHandler.ts +++ b/libs/remix-ai-core/src/helpers/streamHandler.ts @@ -22,7 +22,6 @@ export const HandleStreamResponse = async (streamResponse, cb?: (streamText: string) => void, done_cb?: (result: string) => void) => { try { - console.log("streamResponse handler", streamResponse) let resultText = '' const parser = new JsonStreamParser(); const reader = streamResponse.body!.getReader(); diff --git a/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts b/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts index bbb78b3686..f8e816bc65 100644 --- a/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts +++ b/libs/remix-ai-core/src/inferencers/remote/remoteInference.ts @@ -26,11 +26,11 @@ export class RemoteInferencer implements ICompletions { private async _makeRequest(endpoint, payload, rType:AIRequestType){ this.event.emit("onInference") - const requesURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url + const requestURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url try { const options = { headers: { 'Content-Type': 'application/json', } } - const result = await axios.post(`${requesURL}/${endpoint}`, payload, options) + const result = await axios.post(`${requestURL}/${endpoint}`, payload, options) switch (rType) { case AIRequestType.COMPLETION: @@ -75,7 +75,6 @@ export class RemoteInferencer implements ICompletions { if (payload.return_stream_response) { return response } - const reader = response.body!.getReader(); const decoder = new TextDecoder(); @@ -116,20 +115,16 @@ export class RemoteInferencer implements ICompletions { } } - - async code_completion(prompt, options:IParams=CompletionParams): Promise { const payload = { prompt, "endpoint":"code_completion", ...options } - if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.COMPLETION) - else return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) + return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) } async code_insertion(msg_pfx, msg_sfx, options:IParams=InsertionParams): Promise { // const payload = { "data":[msg_pfx, "code_insertion", msg_sfx, 1024, 0.5, 0.92, 50]} const payload = {"endpoint":"code_insertion", msg_pfx, msg_sfx, ...options, prompt: '' } - if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.COMPLETION) - else return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) + return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) } async code_generation(prompt, options:IParams=GenerationParams): Promise { @@ -156,6 +151,7 @@ export class RemoteInferencer implements ICompletions { async error_explaining(prompt, options:IParams=GenerationParams): Promise { const payload = { prompt, "endpoint":"error_explaining", ...options } + console.log("payload: ", payload) if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload , AIRequestType.GENERAL) else return this._makeRequest(payload.endpoint, payload, AIRequestType.GENERAL) } diff --git a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts index 79f995dd6e..4a792bad4b 100644 --- a/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts +++ b/libs/remix-ui/editor/src/lib/providers/inlineCompletionProvider.ts @@ -1,5 +1,6 @@ /* eslint-disable no-control-regex */ import { EditorUIProps, monacoTypes } from '@remix-ui/editor'; +import { JsonStreamParser } from '@remix/remix-ai-core'; const _paq = (window._paq = window._paq || []) export class RemixInLineCompletionProvider implements monacoTypes.languages.InlineCompletionsProvider { @@ -81,6 +82,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli const data = await this.props.plugin.call('remixAI', 'code_insertion', word, word_after) this.task = 'code_generation' + console.log("data: " + this.task, data) const parsedData = data.trimStart() //JSON.parse(data).trimStart() const item: monacoTypes.languages.InlineCompletion = { @@ -131,6 +133,7 @@ export class RemixInLineCompletionProvider implements monacoTypes.languages.Inli } } catch (err){ + console.log("err: " + err) return } } diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index d206c186b9..37be0a07ab 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -776,6 +776,8 @@ export const EditorUI = (props: EditorUIProps) => { const file = await props.plugin.call('fileManager', 'getCurrentFile') const content = await props.plugin.call('fileManager', 'readFile', file) const message = intl.formatMessage({ id: 'editor.generateDocumentationByAI' }, { content, currentFunction: currentFunction.current }) + + // do not stream this response const cm = await await props.plugin.call('remixAI', 'code_explaining', message) const natSpecCom = "\n" + extractNatspecComments(cm) @@ -827,9 +829,10 @@ export const EditorUI = (props: EditorUIProps) => { ], run: async () => { const file = await props.plugin.call('fileManager', 'getCurrentFile') - const content = await props.plugin.call('fileManager', 'readFile', file) - const message = intl.formatMessage({ id: 'editor.explainFunctionByAI' }, { content, currentFunction: currentFunction.current }) - await props.plugin.call('remixAI', 'code_explaining', message, content) + const context = await props.plugin.call('fileManager', 'readFile', file) + const message = intl.formatMessage({ id: 'editor.explainFunctionByAI' }, { content:context, currentFunction: currentFunction.current }) + // await props.plugin.call('remixAI', 'code_explaining', message, context) + await props.plugin.call('remixAI' as any, 'chatPipe', 'code_explaining', message, context) _paq.push(['trackEvent', 'ai', 'remixAI', 'explainFunction']) }, } @@ -848,8 +851,10 @@ export const EditorUI = (props: EditorUIProps) => { const file = await props.plugin.call('fileManager', 'getCurrentFile') const content = await props.plugin.call('fileManager', 'readFile', file) const selectedCode = editor.getModel().getValueInRange(editor.getSelection()) + const pipeMessage = intl.formatMessage({ id: 'editor.ExplainPipeMessage' }, { content:selectedCode }) - await props.plugin.call('remixAI', 'code_explaining', selectedCode, content) + await props.plugin.call('remixAI' as any, 'chatPipe', 'code_explaining', selectedCode, content, pipeMessage) + // await props.plugin.call('remixAI', 'code_explaining', selectedCode, content) _paq.push(['trackEvent', 'ai', 'remixAI', 'explainFunction']) }, } diff --git a/libs/remix-ui/remix-ai/src/lib/components/Default.tsx b/libs/remix-ui/remix-ai/src/lib/components/Default.tsx index 3e7a680063..3d52b93a56 100644 --- a/libs/remix-ui/remix-ai/src/lib/components/Default.tsx +++ b/libs/remix-ui/remix-ai/src/lib/components/Default.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState, useCallback} from 'react' import '../remix-ai.css' import { DefaultModels, GenerationParams, ChatHistory, HandleStreamResponse, HandleSimpleResponse } from '@remix/remix-ai-core'; -import { StreamSend, StreamingAdapterObserver, useAiChatApi } from '@nlux/react'; +import { ConversationStarter, StreamSend, StreamingAdapterObserver, useAiChatApi } from '@nlux/react'; import axios from 'axios'; import { AiChat, useAsStreamAdapter, ChatItem, AiChatUI} from '@nlux/react'; import '@nlux/themes/nova.css'; @@ -29,7 +29,6 @@ export const Default = (props) => { response = await props.plugin.call('remixAI', 'solidity_answer', prompt, GenerationParams); } - if (GenerationParams.return_stream_response) HandleStreamResponse(response, (text) => {observer.next(text)}, (result) => { @@ -44,6 +43,9 @@ export const Default = (props) => { }; ChatApi = useAiChatApi(); + const conversationStarters: ConversationStarter[] = [ + {prompt: 'Explain what is a solidity contract!', icon: ⭐️}, + {prompt: 'Explain the current file in Editor'}] // Define initial messages const initialMessages: ChatItem[] = [ @@ -68,8 +70,8 @@ export const Default = (props) => { }} //initialConversation={initialMessages} - conversationOptions={{ layout: 'bubbles' }} - displayOptions={{ colorScheme: "auto" }} + conversationOptions={{ layout: 'bubbles', conversationStarters }} + displayOptions={{ colorScheme: "auto", themeId: "nova" }} composerOptions={{ placeholder: "Type your query", submitShortcut: 'Enter', hideStopButton: false, diff --git a/libs/remix-ui/renderer/src/lib/renderer.tsx b/libs/remix-ui/renderer/src/lib/renderer.tsx index 984f180e0f..adff994774 100644 --- a/libs/remix-ui/renderer/src/lib/renderer.tsx +++ b/libs/remix-ui/renderer/src/lib/renderer.tsx @@ -75,7 +75,8 @@ export const Renderer = ({ message, opt = {}, plugin }: RendererProps) => { try { const content = await plugin.call('fileManager', 'readFile', editorOptions.errFile) const message = intl.formatMessage({ id: 'solidity.openaigptMessage' }, { content, messageText }) - await plugin.call('remixAI', 'error_explaining', message) + // await plugin.call('remixAI', 'error_explaining', message) + await plugin.call('remixAI' as any, 'chatPipe', 'error_explaining', message) _paq.push(['trackEvent', 'ai', 'remixAI', 'error_explaining_SolidityError']) } catch (err) { console.error('unable to askGtp') diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 79bea21536..7acb725185 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -250,7 +250,35 @@ export const TabsUI = (props: TabsUIProps) => { const content = await props.plugin.call('fileManager', 'readFile', path) if (tabsState.currentExt === 'sol') { setExplaining(true) + // if plugin is pinned, + if (await props.plugin.call('pinnedPanel', 'currentFocus') === 'remixAI'){ + console.log("pinned has focus") + await props.plugin.call('remixAI', 'chatPipe', 'code_explaining', content) + } + else{ + const profile = { + name: 'remixAI', + displayName: 'Remix AI', + methods: ['code_generation', 'code_completion', + "solidity_answer", "code_explaining", + "code_insertion", "error_explaining", + "initialize", 'chatPipe', 'ProcessChatRequestBuffer', 'isChatRequestPending'], + events: [], + icon: 'assets/img/remix-logo-blue.png', + description: 'RemixAI provides AI services to Remix IDE.', + kind: '', + location: 'sidePanel', + documentation: 'https://remix-ide.readthedocs.io/en/latest/remixai.html', + maintainedBy: 'Remix' + } + console.log("pinned does not have focus") + // await props.plugin.call('sidePanel', 'focus', 'remixAI') + await props.plugin.call('sidePanel', 'pinView', profile) + setTimeout(async () => { await props.plugin.call('remixAI', 'chatPipe', 'code_explaining', content) + }, 500) + } + // await props.plugin.call('remixAI', 'code_explaining', content) setExplaining(false) _paq.push(['trackEvent', 'ai', 'remixAI', 'explain_file']) diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index a17d6846cb..e7a4feb0aa 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -238,11 +238,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { // TODO: rm gpt or redirect gpt to sol-pgt } else if (script.trim().startsWith('gpt')) { call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) - await call('remixAI', 'solidity_answer', script) + await call('remixAI', 'solidity_answer', script) // No streaming supported in terminal _paq.push(['trackEvent', 'ai', 'remixAI', 'askFromTerminal']) } else if (script.trim().startsWith('sol-gpt')) { call('terminal', 'log',{ type: 'warn', value: `> ${script}` }) - await call('remixAI', 'solidity_answer', script) + await call('remixAI', 'solidity_answer', script) // No streaming supported in terminal _paq.push(['trackEvent', 'ai', 'remixAI', 'askFromTerminal']) } else { await call('scriptRunner', 'execute', script)