Skip to content
5 changes: 3 additions & 2 deletions apps/remix-ide-e2e/src/tests/ai_panel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ module.exports = {
'Should contain message starters #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('remixaiassistant')
.waitForElementVisible('*[data-id="movePluginToRight"]')
.click('*[data-id="movePluginToRight"]')
.waitForElementVisible('*[data-id="remix-ai-assistant-starter-0"]')
.click('*[data-id="remix-ai-assistant-starter-0"]')
.waitForElementVisible('*[data-id="remix-ai-assistant"]')
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//div[contains(@class,"chat-bubble") and contains(.,"Explain what a modifier is")]'
selector: '//*[contains(@class,"chat-bubble") and contains(.,"Explain what a modifier is")]'
})
.waitForElementPresent({
locateStrategy: 'xpath',
Expand All @@ -55,7 +57,6 @@ module.exports = {
locateStrategy: 'xpath',
selector: "//*[@data-id='remix-ai-streaming' and @data-streaming='false']"
})

},
'Should select the AI assistant provider #group1': function (browser: NightwatchBrowser) {
browser
Expand Down
3 changes: 0 additions & 3 deletions apps/remix-ide/src/app/components/side-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,6 @@ export class SidePanel extends AbstractPanel {
*/
async showContent(name) {
super.showContent(name)
if (name === 'remixaiassistant') { // TODO: should this be a plugin feature?
this.pinView(this.plugins['remixaiassistant'].profile)
}
this.emit('focusChanged', name)
this.renderComponent()
}
Expand Down
5 changes: 3 additions & 2 deletions apps/remix-ide/src/app/plugins/remix-ai-assistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EventEmitter } from 'events'
const profile = {
name: 'remixaiassistant',
displayName: 'Remix AI Assistant',
icon: 'assets/img/remixai-logoAI.webp',
icon: 'assets/img/remixai-logoAI-updated.webp',
description: 'AI code assistant for Remix IDE',
kind: 'remixaiassistant',
location: 'sidePanel',
Expand Down Expand Up @@ -103,7 +103,8 @@ export class RemixAIAssistant extends ViewPlugin {
}

async handleActivity(type: string, payload: any) {
await this.call('layout', 'maximisePinnedPanel')
console.log('RemixAiAssistant activity:', type, payload)
// await this.call('layout', 'maximisePinnedPanel')
}

updateComponent(state: {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 15 additions & 2 deletions libs/remix-ui/remix-ai-assistant/src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const ChatHistoryComponent: React.FC<ChatHistoryComponentProps> = ({

{/* Bubble */}
<div data-id="ai-response-chat-bubble-section" className="overflow-y-scroll w-100 mr-1">
<div className={`chat-bubble p-2 rounded ${bubbleClass}`}>
<div className={`chat-bubble p-2 rounded ${bubbleClass}`} data-id="ai-user-chat-bubble">
{msg.role === 'user' && (
<small className="text-uppercase fw-bold text-secondary d-block mb-1">
You
Expand Down Expand Up @@ -118,12 +118,25 @@ export const ChatHistoryComponent: React.FC<ChatHistoryComponentProps> = ({
</pre>
</div>
)
}
},
ul: ({ node, ...props }) => (
<ul
{...props}
style={{
listStylePosition: 'inside',
paddingLeft: '0.5rem'
}}
/>
),
li: ({ node, ...props }) => (
<li {...props} style={{ padding: '1px' }} />
)
}}
>
{msg.content}
</ReactMarkdown>
) : (

msg.content
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export default function GroupListMenu(props: GroupListMenuProps) {
}}
>
<div className="d-flex flex-column small text-left">
<span className="font-semibold text-white mb-1">{item.label}</span>
<span className="font-semibold text-secondary mb-1">{item.label}</span>
<div className="d-flex justify-content-between">
<span className="text-light mr-2 text-wrap">{item.bodyText}</span>{ props.choice === item.stateValue && <span className={item.icon}></span> }
<span className="text-secondary mr-2 text-wrap">{item.bodyText}</span>{ props.choice === item.stateValue && <span className={item.icon}></span> }
</div>
</div>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, RefObject } from 'react'

export function useOnClickOutside(
refs: RefObject<HTMLElement>[],
handler: () => void
) {
useEffect(() => {
const controller = new AbortController()
const listener = (e: MouseEvent) => {
// if click landed in *any* ref, ignore
if (refs.some(ref => ref.current?.contains(e.target as Node))) {
return
}
handler()
}

// listen for click (not mousedown)
document.addEventListener('click', listener, { signal: controller.signal })
return () => {
controller.abort()
};
}, [refs, handler])
}
43 changes: 36 additions & 7 deletions libs/remix-ui/remix-ai-assistant/src/components/prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { MutableRefObject, Ref, useEffect, useRef, useState } from 'react
import GroupListMenu from "./contextOptMenu"
import { AiContextType, groupListType } from '../types/componentTypes'
import { AiAssistantType } from '../types/componentTypes'
import { CustomTooltip } from "@remix-ui/helper"

// PromptArea component
export interface PromptAreaProps {
Expand Down Expand Up @@ -30,6 +31,8 @@ export interface PromptAreaProps {
aiAssistantGroupList: groupListType[]
}

const _paq = (window._paq = window._paq || [])

export const PromptArea: React.FC<PromptAreaProps> = ({
input,
setInput,
Expand Down Expand Up @@ -73,23 +76,33 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
)}

<div
className="prompt-area d-flex flex-column gap-2 w-100 p-3 border border-text remix-aichat-background align-self-start"
className="prompt-area d-flex flex-column gap-2 w-100 p-3 border border-text bg-light align-self-start"
>
<div className="d-flex justify-content-between mb-3 border border-right-0 border-left-0 border-top-0 border-bottom pb-1">
<button
onClick={handleAddContext}
data-id="composer-ai-add-context"
className="btn btn-dim btn-sm text-light small font-weight-light border border-text rounded"
className="btn btn-dim btn-sm text-secondary small font-weight-light border border-text rounded"
ref={contextBtnRef}
>
@Add context
</button>

<span
className="badge align-self-center badge-info text-ai font-weight-light rounded"
>
Ai Beta
</span>
<div className="d-flex justify-content-center align-items-center">
<CustomTooltip
tooltipText={<TooltipContent />}
>
<span
className="far fa-circle-info text-ai mr-1"
onMouseEnter={_paq.push(['trackEvent', 'remixAI', 'AICommandTooltip', 'User clicked on AI command info'])}
></span>
</CustomTooltip>
<span
className="badge align-self-center badge-info text-ai font-weight-light rounded"
>
Ai Beta
</span>
</div>
</div>
<div className="ai-chat-input d-flex flex-column">
<input
Expand Down Expand Up @@ -171,3 +184,19 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
</>
)
}

function TooltipContent () {
return (
<ul className="list-unstyled p-2 mr-3">
<li className="">
{'- Use @workspace <text> or /w <text> to manage or edit files within your workspace'}
</li>
<li className="">
{"- Use @generate <text> or /g <text> to generate contracts or scripts in a workspace. In case the generation result doesn't compile, use the /continue or /c command to generate similar solutions until a solution that compiles successfully is suggested."}
</li>
<li className="">
{'- Alternatively, you may type your question directly below.'}
</li>
</ul>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ChatHistoryComponent } from './chat'
import { ActivityType, ChatMessage } from '../lib/types'
import { groupListType } from '../types/componentTypes'
import GroupListMenu from './contextOptMenu'
import { useOnClickOutside } from './onClickOutsideHook'

const _paq = (window._paq = window._paq || [])

Expand Down Expand Up @@ -51,6 +52,9 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
const modelBtnRef = useRef(null)
const contextBtnRef = useRef(null)

useOnClickOutside([modelBtnRef, contextBtnRef], () => setShowAssistantOptions(false))
useOnClickOutside([modelBtnRef, contextBtnRef], () => setShowContextOptions(false))

const getBoundingRect = (ref: MutableRefObject<any>) => ref.current?.getBoundingClientRect()
const calcAndConvertToDvh = (coordValue: number) => (coordValue / window.innerHeight) * 100
const calcAndConvertToDvw = (coordValue: number) => (coordValue / window.innerWidth) * 100
Expand Down Expand Up @@ -390,9 +394,9 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
title: 'Generate Workspace',
message: `
Describe the kind of workspace you want RemixAI to scaffold. For example:
ERC‑20 token with foundry tests
ERC‑721 NFT collection with IPFS metadata
Decentralized voting app with Solidity smart contracts
Create an ERC‑20 token with all explanations as comments in the contract.
Create a Voting contract and explain the contract with comments
Create a proxy contract with all explanations about the contract as comments

`,
modalType: ModalTypes.prompt, // single-line text
Expand Down