Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions libs/remix-ai-core/src/agents/workspaceAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,51 @@ export class workspaceAgent {
if (typeof payload === 'string') {
payload = JSON.parse(payload)
}
let modifiedFilesMarkdown = '### List of Modified Files\n'
let modifiedFilesMarkdown = '#### **List of Modified Files**\n'
let createdFilesMarkdown = '#### **List of Created Files**\n'
let hasCreatedFiles = false

for (const file of payload.files) {
if (!Object.values(SupportedFileExtensions).some(ext => file.fileName.endsWith(ext))) continue;
// const fileContent = await this.plugin.call('codeFormatter', 'format', file.fileName, file.content, true);
await statusCallback?.(`Showing diff for ${file.fileName}...`)
await this.plugin.call('editor', 'showCustomDiff', file.fileName, file.content)
modifiedFilesMarkdown += `- ${file.fileName}\n`

try {
const fileExists = await this.plugin.call('fileManager', 'exists', file.fileName)

if (fileExists) {
await this.plugin.call('editor', 'showCustomDiff', file.fileName, file.content)
modifiedFilesMarkdown += `- ${file.fileName}\n`
} else {
await statusCallback?.(`Creating file ${file.fileName}...`)

const dirPath = file.fileName.substring(0, file.fileName.lastIndexOf('/'))
if (dirPath && dirPath.length > 0) {
try {
await this.plugin.call('fileManager', 'mkdir', dirPath)
} catch (mkdirError) {
// Directory already exist, just continue
}
}

await this.plugin.call('fileManager', 'writeFile', file.fileName, "")
await new Promise(resolve => setTimeout(resolve, 2000)) // wait so the content is written before diffing
await this.plugin.call('editor', 'showCustomDiff', file.fileName, file.content)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you don't need to show a custom diff in that case as the file is new.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yann300 actually you need to show a diff to be compliant to make users asserting any changes

createdFilesMarkdown += `- ${file.fileName}\n`
hasCreatedFiles = true
}
} catch (fileError) {
console.warn(`Error processing file ${file.fileName}:`, fileError)
}
}

await statusCallback?.('Workspace modifications complete!')
return modifiedFilesMarkdown

// Build result markdown
let result = modifiedFilesMarkdown
if (hasCreatedFiles) {
result += '\n' + createdFilesMarkdown
}

return result || 'No files modified'
} catch (error) {
console.warn('Error writing generation results:', error);
return 'No files modified'
Expand Down
16 changes: 1 addition & 15 deletions libs/remix-ui/remix-ai-assistant/src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { CustomTooltip } from "@remix-ui/helper"

const DEFAULT_SUGGESTIONS = [
'What is a modifier?',
'What is a UniSwap hook?',
'What is a Uniswap hook?',
'What is a ZKP?'
]

Expand All @@ -31,20 +31,6 @@ const AiChatIntro = (props) => {
RemixAI provides you personalized guidance as you build. It can break down concepts,
answer questions about blockchain technology and assist you with your smart contracts.
</p>
<div className="d-flex flex-column" style={{ fontSize: '0.9rem' }}>
<div className="d-flex flex-row align-items-center">
<span className="font-italic m-1">{`<prompt>: `}</span>
<span>ask your question</span></div>
<div className="d-flex flex-row align-items-center">
<span className="font-italic m-1">{`/w <prompt>: `}</span>
<span>modify your code</span></div>
<div className="d-flex flex-row align-items-center">
<span className="font-italic m-1">{`/c <prompt>: `}</span>
<span>continue fixing compilation</span></div>
<div className="d-flex flex-row align-items-center">
<span className="font-italic m-1">{`/g <prompt>: `}</span>
<span>generate a new workspace</span></div>
</div>
<div className="d-flex flex-column mt-3">
{DEFAULT_SUGGESTIONS.map((s, index) => (
<button
Expand Down
67 changes: 38 additions & 29 deletions libs/remix-ui/remix-ai-assistant/src/components/prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface PromptAreaProps {
aiAssistantGroupList: groupListType[]
textareaRef?: React.RefObject<HTMLTextAreaElement>
maximizePanel: () => Promise<void>
aiMode: 'ask' | 'edit'
setAiMode: React.Dispatch<React.SetStateAction<'ask' | 'edit'>>
}

const _paq = (window._paq = window._paq || [])
Expand Down Expand Up @@ -73,7 +75,9 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
aiContextGroupList,
aiAssistantGroupList,
textareaRef,
maximizePanel
maximizePanel,
aiMode,
setAiMode
}) => {

return (
Expand Down Expand Up @@ -103,22 +107,38 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
className="btn btn-dim btn-sm text-secondary small fw-light border border-text rounded"
ref={contextBtnRef}
>
<span>{}</span>{contextChoice === 'none' && <span data-id="aiContext-file">{'@ Add Context'}</span>}
<span>{}</span>{contextChoice === 'none' && <span data-id="aiContext-file">{'Select Context'}</span>}
{contextChoice === 'workspace' && <span data-id="aiContext-workspace">{'Workspace'}</span>}
{contextChoice === 'opened' && <span data-id="aiContext-opened">{'Open Files'}</span>}
{contextChoice === 'current' && <span data-id="aiContext-current">{'Current File'}</span>}
</button>

<div className="d-flex justify-content-center align-items-center">
<CustomTooltip
tooltipText={<TooltipContent />}
delay={{ show: 1000, hide: 0 }}
>
<span
className="far fa-circle-info text-ai me-1"
onMouseEnter={() => _paq.push(['trackEvent', 'remixAI', 'AICommandTooltip', 'User clicked on AI command info'])}
></span>
</CustomTooltip>
<div className="d-flex justify-content-center align-items-center gap-2">
{/* Ask/Edit Mode Toggle */}
<div className="btn-group btn-group-sm" role="group">
<button
type="button"
className={`btn ${aiMode === 'ask' ? 'btn-primary' : 'btn-outline-secondary'} px-2`}
onClick={() => {
setAiMode('ask')
_paq.push(['trackEvent', 'remixAI', 'ModeSwitch', 'ask'])
}}
title="Ask mode - Chat with AI"
>
Ask
</button>
<button
type="button"
className={`btn ${aiMode === 'edit' ? 'btn-primary' : 'btn-outline-secondary'} px-2`}
onClick={() => {
setAiMode('edit')
_paq.push(['trackEvent', 'remixAI', 'ModeSwitch', 'edit'])
}}
title="Edit mode - Edit workspace code"
>
Edit
</button>
</div>
<span
className="badge align-self-center text-bg-info fw-light rounded"
>
Expand All @@ -145,7 +165,11 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
onKeyDown={e => {
if (e.key === 'Enter' && !isStreaming) handleSend()
}}
placeholder="Ask me anything, add workspace files..."
placeholder={
aiMode === 'ask'
? "Select context and ask me anything!"
: "Edit my codebase, generate new contracts ..."
}
/>

<div className="d-flex justify-content-between">
Expand Down Expand Up @@ -182,7 +206,7 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
className="btn btn-text btn-sm small fw-light text-secondary mt-2 align-self-end border border-text rounded"
onClick={handleGenerateWorkspace}
>
{'@Generate'}
{'Create new workspace with AI'}
</button>
{/* <button
className={input.length > 0 ? 'btn bg-ai border-text border btn-sm fw-light text-secondary mt-2 align-self-end' : 'btn btn-text border-text border btn-sm fw-light text-secondary mt-2 align-self-end disabled'}
Expand Down Expand Up @@ -225,18 +249,3 @@ export const PromptArea: React.FC<PromptAreaProps> = ({
)
}

function TooltipContent () {
return (
<ul className="list-unstyled p-2 me-3">
<li className="">
{'- Use /w <prompt> : To manage or edit files within your workspace'}
</li>
<li className="">
{'- Alternatively, you may type your question directly below.'}
</li>
<li className="">
{'-See Ollama Setup Guide'}
</li>
</ul>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
const [availableModels, setAvailableModels] = useState<string[]>([])
const [selectedModel, setSelectedModel] = useState<string | null>(null)
const [isOllamaFailureFallback, setIsOllamaFailureFallback] = useState(false)
const [aiMode, setAiMode] = useState<'ask' | 'edit'>('ask')

const historyRef = useRef<HTMLDivElement | null>(null)
const modelBtnRef = useRef(null)
Expand Down Expand Up @@ -416,11 +417,23 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
},
[isStreaming, props.plugin]
)
const handleGenerateWorkspaceWithPrompt = useCallback(async (prompt: string) => {
dispatchActivity('button', 'generateWorkspace')
if (prompt && prompt.trim()) {
await sendPrompt(`/workspace ${prompt.trim()}`)
_paq.push(['trackEvent', 'remixAI', 'GenerateNewAIWorkspaceFromEditMode', prompt])
}
}, [sendPrompt])

const handleSend = useCallback(async () => {
await sendPrompt(input)
if (aiMode === 'ask') {
await sendPrompt(input)
} else if (aiMode === 'edit') {
// Call generateWorkspace for edit mode with the current input
await handleGenerateWorkspaceWithPrompt(input)
}
setInput('')
}, [input, sendPrompt])
}, [input, sendPrompt, aiMode, handleGenerateWorkspaceWithPrompt])

// Added handlers for special command buttons (assumed to exist)
const handleAddContext = useCallback(() => {
Expand Down Expand Up @@ -734,6 +747,8 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
aiContextGroupList={aiContextGroupList}
aiAssistantGroupList={aiAssistantGroupList}
textareaRef={textareaRef}
aiMode={aiMode}
setAiMode={setAiMode}
/>
</section>
</div>
Expand Down