popup for ai

pull/5371/head
lianahus 3 weeks ago
parent 83f1338d18
commit 6ffdb154f6
  1. 54
      apps/remix-ide/src/app/components/popup-panel.tsx
  2. 2
      apps/remix-ide/src/app/plugins/electron/remixAIDesktopPlugin.tsx
  3. 44
      apps/remix-ide/src/app/plugins/remixAIPlugin.tsx
  4. 2
      apps/remixdesktop/src/plugins/remixAIDektop.ts
  5. 1
      libs/remix-api/src/lib/plugins/remixai-api.ts
  6. 8
      libs/remix-api/src/lib/remix-api.ts
  7. 15
      libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx
  8. 4
      libs/remix-ui/remix-ai/src/lib/components/Default.tsx
  9. 18
      libs/remix-ui/remix-ai/src/lib/components/RemixAI.tsx
  10. 36
      libs/remix-ui/remix-ai/src/lib/components/color.css
  11. 5
      libs/remix-ui/statusbar/src/css/statusbar.css
  12. 53
      libs/remix-ui/statusbar/src/lib/components/aiStatus.tsx
  13. 14
      libs/remix-ui/statusbar/src/lib/components/scamDetails.tsx
  14. 22
      libs/remix-ui/statusbar/src/lib/remixui-statusbar-panel.tsx
  15. 2
      libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx

@ -3,20 +3,26 @@ import { AbstractPanel } from './panel'
import { RemixPluginPanel } from '@remix-ui/panel'
import packageJson from '../../../../../package.json'
import { PluginViewWrapper } from '@remix-ui/helper'
import {EventEmitter} from 'events'
const profile = {
name: 'popupPanel',
displayName: 'Popup Panel',
description: 'Remix IDE popup panel',
version: packageJson.version,
methods: ['addView', 'removeView', 'showContent']
events: ['popupPanelShown'],
methods: ['addView', 'removeView', 'showContent', 'showPopupPanel']
}
export class PopupPanel extends AbstractPanel {
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => {}
showPanel: boolean
constructor(config) {
super(profile)
this.event = new EventEmitter()
this.showPanel = true
}
setDispatch(dispatch: React.Dispatch<any>) {
@ -49,21 +55,59 @@ export class PopupPanel extends AbstractPanel {
this.renderComponent()
}
async showPopupPanel(show) {
console.log("hide in popup-panel =", show)
this.showPanel = show
this.event.emit('popupPanelShown', show)
this.renderComponent()
}
renderComponent() {
this.dispatch({
plugins: this.plugins
plugins: this.plugins,
showPpPanel: this.showPanel
})
}
render() {
return (
<div style={{ height: '200', width: '200', position: 'fixed', bottom: 0, left: 0 }} data-id="popupPanelPluginsContainer">
<PluginViewWrapper plugin={this} />
<div
className={'px-0 bg-light border-info ' + (!this.showPanel ? 'd-none' : 'd-flex')}
style={{
maxHeight: '40rem',
maxWidth: '25rem',
width: 'max-content',
height: '40rem',
position: 'fixed',
bottom: '2rem',
right: '3.5rem',
zIndex: 2000,
boxShadow: '3px 3px var(--secondary), -0.1em 0 1.4em var(--secondary)'
}}
data-id="popupPanelPluginsContainer"
>
{this.showPanel && <PluginViewWrapper plugin={this} />}
</div>
)
}
updateComponent(state: any) {
return <RemixPluginPanel header={<></>} plugins={state.plugins} />
return (
<div className={!state.showPpPanel ? 'd-none' : 'd-flex'}>
<RemixPluginPanel
header={
<span id='menubarAIChat' className='pb-2 d-flex flex-row'>
<button
className='btn fas fa-angle-double-down'
onClick={async () => {
console.log("hide")
await this.showPopupPanel(false)
}}
>
</button>
</span>
}
plugins={state.plugins} />
</div>)
}
}

@ -10,7 +10,7 @@ const desktop_profile = {
description: 'RemixAI provides AI services to Remix IDE Desktop.',
documentation: 'https://remix-ide.readthedocs.io/en/latest/remixai.html',
icon: 'assets/img/remix-logo-blue.png',
methods: ['initializeModelBackend', 'code_completion', 'code_insertion', 'code_generation', 'code_explaining', 'error_explaining', 'solidity_answer'],
methods: ['initializeModelBackend', 'code_completion', 'code_insertion', 'code_generation', 'code_explaining', 'error_explaining', 'solidity_answer', 'toggle'],
}
export class remixAIDesktopPlugin extends ElectronPlugin {

@ -5,10 +5,15 @@ import { RemixAITab, ChatApi } from '@remix-ui/remix-ai'
import React, { useCallback } from 'react';
import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent } from '@remix/remix-ai-core';
import { CustomRemixApi } from '@remix-api'
import { PluginViewWrapper } from '@remix-ui/helper'
type chatRequestBufferT<T> = {
[key in keyof T]: T[key]
}
enum AIChatViewState {
minimized = 0,
open = 1
}
const profile = {
name: 'remixAI',
@ -16,7 +21,8 @@ const profile = {
methods: ['code_generation', 'code_completion',
"solidity_answer", "code_explaining",
"code_insertion", "error_explaining",
"initialize", 'chatPipe', 'ProcessChatRequestBuffer', 'isChatRequestPending'],
"initialize", 'chatPipe', 'ProcessChatRequestBuffer',
'isChatRequestPending', 'toggle'],
events: [],
icon: 'assets/img/remix-logo-blue.png',
description: 'RemixAI provides AI services to Remix IDE.',
@ -37,6 +43,7 @@ export class RemixAIPlugin extends ViewPlugin {
chatRequestBuffer: chatRequestBufferT<any> = null
agent: CodeExplainAgent
useRemoteInferencer:boolean = false
dispatch: any
constructor(inDesktop:boolean) {
super(profile)
@ -46,6 +53,7 @@ export class RemixAIPlugin extends ViewPlugin {
}
onActivation(): void {
//this.renderComponent(AIChatViewState.open)
if (this.isOnDesktop) {
console.log('Activating RemixAIPlugin on desktop')
// this.on(this.remixDesktopPluginName, 'activated', () => {
@ -59,6 +67,10 @@ export class RemixAIPlugin extends ViewPlugin {
}
}
toggle (open: AIChatViewState) {
this.renderComponent(open)
}
async initialize(model1?:IModel, model2?:IModel, remoteModel?:IRemoteModel, useRemote?:boolean){
if (this.isOnDesktop && !this.useRemoteInferencer) {
// on desktop use remote inferencer -> false
@ -201,13 +213,41 @@ export class RemixAIPlugin extends ViewPlugin {
return ""
}
}
isChatRequestPending(){
return this.chatRequestBuffer != null
}
setDispatch(dispatch) {
this.dispatch = dispatch
this.renderComponent(AIChatViewState.open)
}
renderComponent (open: AIChatViewState) {
this.dispatch({
plugin: this,
openState: open
})
}
render() {
return <div
id='ai-view'
className='pb-2 mb-2 h-100 d-flex'
data-id='aichat-view'
style={{
minHeight: 'max-content',
maxWidth: '25rem',
width: '24rem',
}}
>
<PluginViewWrapper plugin={this} />
</div>
}
updateComponent(state) {
return (
<RemixAITab plugin={this}></RemixAITab>
<RemixAITab plugin={state.plugin} openState={state.openState}></RemixAITab>
)
}
}

@ -32,7 +32,7 @@ const clientProfile: Profile = {
description: 'RemixAI provides AI services to Remix IDE Desktop.',
kind: '',
documentation: 'https://remix-ide.readthedocs.io/en/latest/remixai.html',
methods: ['initializeModelBackend', 'code_completion', 'code_insertion', 'code_generation', 'code_explaining', 'error_explaining', 'solidity_answer']
methods: ['initializeModelBackend', 'code_completion', 'code_insertion', 'code_generation', 'code_explaining', 'error_explaining', 'solidity_answer', 'toggle']
}
class RemixAIDesktopPluginClient extends ElectronBasePluginClient {

@ -19,5 +19,6 @@ export interface IRemixAI {
chatPipe(pipeMessage: string): Promise<void>,
ProcessChatRequestBuffer(params:IParams): Promise<void>,
initialize(model1?:IModel, model2?:IModel, remoteModel?:IRemoteModel, useRemote?:boolean): Promise<void>,
toggle(boolean)
}
}

@ -16,8 +16,15 @@ import { IMatomoApi } from "./plugins/matomo-api"
import { IRemixAI } from "./plugins/remixai-api"
import { IRemixAID } from "./plugins/remixAIDesktop-api"
import { IDgitPlugin } from "./plugins/dgitplugin-api"
import { Api } from "@remixproject/plugin-utils";
export interface ICustomRemixApi extends IRemixApi {
popupPanel: {
methods: ['showPopupPanel']
events: ['popupPanelShown']
showPopupPanel(): void
} & Api
dgitApi: IGitApi
dgit: IDgitPlugin
config: IConfigApi
@ -37,4 +44,5 @@ export interface ICustomRemixApi extends IRemixApi {
remixAID: IRemixAID
}
export declare type CustomRemixApi = Readonly<ICustomRemixApi>

@ -14,16 +14,21 @@ export interface RemixPanelProps {
export function RemixPluginPanel(props: RemixPanelProps) {
return (
<>
<div className='d-flex flex-column'>
{props.header}
<div className="pluginsContainer">
<div className="plugins" id="plugins">
{Object.values(props.plugins).map((pluginRecord) => {
return <RemixUIPanelPlugin key={pluginRecord.profile.name} pluginRecord={pluginRecord} initialState={props.pluginState} highlightStamp={props.highlightStamp} />
})}
{ Object.values(props.plugins).map((pluginRecord) => {
return <RemixUIPanelPlugin
key={pluginRecord.profile.name}
pluginRecord={pluginRecord}
initialState={props.pluginState}
highlightStamp={props.highlightStamp}
/>
}) }
</div>
</div>
</>
</div>
)
}

@ -42,8 +42,8 @@ export const Default = (props) => {
};
ChatApi = useAiChatApi();
const conversationStarters: ConversationStarter[] = [
{ prompt: 'Explain briefly the current file in Editor', icon: <span></span> },
{ prompt: 'Explain what is a solidity contract!' }]
{ prompt: 'Explain what is a solidity contract!'},
{ prompt: 'Explain briefly the current file in Editor'}]
// Define initial messages
const initialMessages: ChatItem[] = [

@ -1,14 +1,22 @@
import React, { useContext } from 'react'
import '../remix-ai.css'
import { Default, ChatApi } from './Default'
enum AIChatViewState {
minimized = 0,
open = 1
}
interface IRemixAITab {
plugin: any,
openState: AIChatViewState
}
export const RemixAITab = (props: IRemixAITab) => {
export const RemixAITab = (props) => {
const plugin = props.plugin
return (
<>
<div id="remixAITab" className="px-2 pb-4">
<Default plugin={plugin}></Default>
<div id="remixAITab" className="h-100 w-100 px-2 pb-1">
<div className='h-100'>
<Default plugin={props.plugin}></Default>
</div>
</div>
</>
)

@ -1,11 +1,32 @@
.nlux-theme-remix_ai_theme[data-color-scheme='light'] {
--nlux-ChatRoom--BackgroundColor: var(--text-background);
padding-bottom: 0.1rem;
}
.nlux-theme-remix_ai_theme[data-color-scheme='dark'] {
--nlux-ChatRoom--BackgroundColor: var(--text-background);
padding-bottom: 0.1rem;
}
.nlux-launchPad-container {
padding-bottom: 2rem;
}
.nlux-conversationStarters-container {
overflow-y: hidden;
}
.nlux-comp-conversationStarters {
width: auto !important;
padding: 0px !important
}
.nlux-conversationStarters-container>.nlux-comp-conversationStarters>.nlux-comp-conversationStarter {
border-width: 1px !important;
border-color: var(--text-background) !important;
}
.nlux-theme-remix_ai_theme {
/* Override top-level chat room colors */
@ -35,9 +56,13 @@
--nlux-PromptInput-Focus-Outline--Width: 10px;
--nlux-PromptInput-Max-Height: 50px;
--nlux-PromptInput--BorderWidth: 0;
.nlux-comp-composer > textarea {padding: 8px;}
--nlux-PromptInput--BorderRadius: 10px 0 0 10px;
--nlux-PromptInput-Height: 50px;
--nlux-PromptInput-Height: 30px;
--nlux-cvStrt--brdrWd: 0;
--nlux-cvStrt--brdClr: var(--light);
--nlux-cvStrt--brdrWd: 'hidden';
--nlux-comp-composer > textarea {padding: 8px;}
/* Override input colors */
@ -49,10 +74,10 @@
--nlux-Composer--Gap: 0;
/* Override submit button colors */
--nlux-SubmitButton--BackgroundColor: var(--primary);
--nlux-SubmitButton-Active--BackgroundColor:var(--primary);
--nlux-SubmitButton--BackgroundColor: var(--light);
--nlux-SubmitButton-Active--BackgroundColor:var(--light);
--nlux-SubmitButton-Disabled--BackgroundColor: var(--dark);
--nlux-SubmitButton-Active--TextColor: var(--text);
--nlux-SubmitButton-Active--TextColor: var(--primary);
--nlux-SubmitButton-Disabled--TextColor: var(--text);
/** Inline code in markdown */
@ -61,7 +86,6 @@
--nlux-InlineCode--Padding: 0 2px;
--nlux-InlineCode--FontSize: 14px;
/*code block */
--nlux-CodeBlock-CopyButton--BackgroundColor: var(--bg-text);
--nlux-CodeBlock-CopyButton--TextColor: var(--text);

@ -1,4 +1,9 @@
.remixui_statusbar_gitstatus {
position: relative;
left: 20rem;
}
.remixui_statusbar_gitstatus
.remixui_statusbar_gitstatus:hover {
cursor: pointer;

@ -12,12 +12,24 @@ interface AIStatusProps {
export default function AIStatus(props: AIStatusProps) {
const [copilotActive, setCopilotActive] = useState(false)
const [expandAIChat, setExpandAIChat] = useState(true)
useEffect(() => {
const popupPanelListener = async (show) => {
setTimeout(() => setExpandAIChat(show), 0);
};
props.plugin.on('popupPanel', 'popupPanelShown', popupPanelListener)
const run = async () => {
const aiActivate = await props.plugin.call('settings', 'get', 'settings/copilot/suggest/activate')
setCopilotActive(aiActivate)
}
run()
return () => {
props.plugin.off('popupPanel', 'popupPanelShown')
}
}, [])
useEffect(() => {
@ -29,15 +41,42 @@ export default function AIStatus(props: AIStatusProps) {
}
run()
}, [props.plugin.isAiActive, props.plugin.isAiActive])
return (
<CustomTooltip
tooltipText={copilotActive ? "RemixAI Copilot enabled" : "RemixAI Copilot disabled. To enable, open a .sol file and toggle the switch at the left-top of the editor"}
>
<div className="d-flex flex-row pr-2 text-white justify-content-center align-items-center">
<span className={copilotActive === false ? "small mx-1 text-white semi-bold" : "small mx-1 text-white semi-bold" }>
<div>
<CustomTooltip
tooltipText={copilotActive ? "Disable RemixAI Copilot" : "Enable RemixAI Copilot. Switch to .sol file to try it."}
>
<span
style={{cursor: 'pointer'}}
className={"small mx-1 bg-info border-0 text-white " + (copilotActive === false ? "semi-bold" : "")}
onClick={async () => {
await props.plugin.call('settings' as any, 'updateCopilotChoice', !copilotActive)
}}
>
{copilotActive === false ? 'RemixAI Copilot (disabled)' : 'RemixAI Copilot (enabled)'}
</span>
</CustomTooltip>
<div className="d-flex text-sm flex-row pr-2 text-white justify-content-center align-items-center">
<button
style={{
position: 'absolute',
bottom: '0.7rem',
right: '0.3rem',
height: '3rem',
width: '3rem',
borderRadius: '50%',
color: 'var(--ai)',
boxShadow: '3px 3px var(--secondary), -0.1em 0 1.4em var(--secondary)'
}}
className='p-1 alert alert-info border border-info fa-solid fa-message-bot'
onClick={async () => {
await props.plugin.call('popupPanel', 'showPopupPanel', !expandAIChat)
}}
>
{expandAIChat ? '1' : '0'}
</button>
</div>
</CustomTooltip>
</div>
)
}
}

@ -18,13 +18,21 @@ export default function ScamDetails ({ refs, floatStyle, scamAlerts }: ScamDetai
return (
<div
ref={refs.setFloating}
style={ floatStyle }
className="px-1 ml-1 mb-1 d-flex w-25 alert alert-warning border border-warning"
style={{
position: 'absolute',
bottom: '-3.3rem',
left: '-4rem',
height: '6rem',
transform: 'translate(88.5px, -80px)',
willChange: 'transform',
boxShadow: '3px 3px var(--secondary), -0.1em 0 1.4em var(--secondary)'
} }
className="p-1 pb-0 mb-1 d-flex alert alert-warning border border-warning"
>
<span className="align-self-center pl-4 mt-1">
<i style={{ fontSize: 'xxx-large', fontWeight: 'lighter' }} className="pr-2 far fa-exclamation-triangle"></i>
</span>
<div className="d-flex flex-column">
<div className="d-flex flex-column pr-4 pt-2">
{scamAlerts && scamAlerts.map((alert, index) => (
<span className="pl-4 mt-1" key={`${alert.url}${index}`}>
{alert.url.length < 1 ? <FormattedMessage id={`home.scamAlertText${index + 1}`} defaultMessage={alert.message} />

@ -72,25 +72,35 @@ export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) {
<StatusBarContextProvider>
{(platform !== appPlatformTypes.desktop) && showScamDetails && (
<FloatingFocusManager context={context} modal={false}>
<ScamDetails refs={refs} floatStyle={{ ...floatingStyles, minHeight: 'auto', alignContent: 'center', paddingRight: '0.5rem' }} getFloatingProps={getFloatingProps} scamAlerts={scamAlerts} />
<ScamDetails
refs={refs}
floatStyle={{
...floatingStyles,
minHeight: 'auto',
alignContent: 'center',
paddingRight: '0.5rem',
bottom: '-8,5rem',
left: '0rem'
}}
getFloatingProps={getFloatingProps}
scamAlerts={scamAlerts}
/>
</FloatingFocusManager>
)}
<div className="d-flex remixui_statusbar_height flex-row bg-info justify-content-between align-items-center">
{ (platform !== appPlatformTypes.desktop) && <div className="remixui_statusbar bg-warning px-2 remixui_statusbar_custom_padding d-flex justify-center align-items-center">
<ScamAlertStatus refs={refs} getReferenceProps={getReferenceProps} />
</div> }
<div className="remixui_statusbar remixui_statusbar_gitstatus">
<GitStatus plugin={statusBarPlugin} gitBranchName={gitBranchName} setGitBranchName={setGitBranchName} />
</div>
<div className="remixui_statusbar"></div>
<div className="remixui_statusbar">
<DidYouKnow />
</div>
<div className="remixui_statusbar"></div>
<div className="remixui_statusbar d-flex align-items-center p-0">
<div className="remixui_statusbar">
<AIStatus plugin={statusBarPlugin} aiActive={lightAiUp} isAiActive={isAiActive} setIsAiActive={setIsAiActive} />
</div>
{ (platform !== appPlatformTypes.desktop) && <div className="remixui_statusbar bg-warning px-2 remixui_statusbar_custom_padding d-flex justify-center align-items-center">
<ScamAlertStatus refs={refs} getReferenceProps={getReferenceProps} />
</div> }
</div>
</div>
</StatusBarContextProvider>

@ -261,7 +261,7 @@ export const TabsUI = (props: TabsUIProps) => {
methods: ['code_generation', 'code_completion',
"solidity_answer", "code_explaining",
"code_insertion", "error_explaining",
"initialize", 'chatPipe', 'ProcessChatRequestBuffer', 'isChatRequestPending'],
"initialize", 'chatPipe', 'ProcessChatRequestBuffer', 'isChatRequestPending', 'toggle'],
events: [],
icon: 'assets/img/remix-logo-blue.png',
description: 'RemixAI provides AI services to Remix IDE.',

Loading…
Cancel
Save