Skip to content
Draft
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
165 changes: 165 additions & 0 deletions cloud-ide/frontend/AI_MODES_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# AI Ask/Agent Modes Implementation

## Overview
This feature implements two distinct AI interaction modes for the Orca Cloud IDE:

### 🔵 Ask Mode
- **Purpose**: Read-only AI assistant for code questions and guidance
- **Capabilities**:
- Read and analyze project files
- Answer questions about code
- Provide suggestions and explanations
- Search through codebase
- **Restrictions**: Cannot modify, write, or delete any files

### 🟣 Agent Mode (Default)
- **Purpose**: Full-featured AI coding assistant
- **Capabilities**:
- All Ask Mode capabilities PLUS:
- Write and edit project files
- Create new files and directories
- Refactor code
- Implement features autonomously
- Make project-wide changes

## Components

### 1. AIModeSwitcher (`app/components/AIModeSwitcher.tsx`)
A toggle component that allows users to switch between Ask and Agent modes.

**Features:**
- Visual toggle with distinct colors (blue for Ask, purple for Agent)
- Icons for each mode
- Mode description tooltip
- Keyboard accessible

**Props:**
```typescript
interface AIModeSwithcerProps {
currentMode: AIMode // 'ask' | 'agent'
onModeChange: (mode: AIMode) => void
}
```

### 2. AIChatPanel (`app/components/AIChatPanel.tsx`)
Chat interface for interacting with the AI assistant.

**Features:**
- Real-time chat interface
- Mode-aware UI (changes color based on mode)
- Message history
- Loading states
- Keyboard shortcuts (Enter to send, Shift+Enter for new line)
- Empty state with helpful prompts

**Props:**
```typescript
interface AIChatPanelProps {
mode: AIMode
projectId: string | null
}
```

### 3. Main IDE Integration (`app/page.tsx`)
The AI components are integrated into the main IDE interface:
- Mode switcher in the top toolbar
- Chat panel in the right sidebar (replacing Inspector)
- State management for AI mode
- WebSocket integration for backend communication

## Usage

### Switching Modes
1. Click the mode toggle in the top toolbar
2. Choose "Ask Mode" for read-only assistance
3. Choose "Agent Mode" for full AI capabilities

### Chatting with AI
1. Type your message in the input box
2. Press Enter to send (Shift+Enter for multi-line)
3. View AI responses in the chat panel

### Mode-Specific Behavior

**In Ask Mode:**
- AI can read and analyze your code
- Ask: "What does this function do?"
- Ask: "How can I improve this code?"
- Ask: "Where is the player movement logic?"

**In Agent Mode:**
- AI can make changes to your code
- Request: "Add error handling to this function"
- Request: "Refactor this component to use hooks"
- Request: "Create a new player class"

## Backend Integration

The frontend sends mode information to the backend via WebSocket:

```typescript
socket.emit('ai-mode-change', { mode: 'ask' | 'agent' })
```

The backend should:
1. Store the current mode per user/session
2. Restrict write operations in Ask mode
3. Allow all operations in Agent mode

## Styling

The design follows Cursor's style guide:
- **Ask Mode**: Blue theme (#3B82F6)
- **Agent Mode**: Purple theme (#9333EA)
- Dark mode UI with gray-800/900 backgrounds
- Smooth transitions and hover states
- Accessible contrast ratios

## Future Enhancements

1. **Mode Persistence**: Save user's mode preference
2. **Mode-specific suggestions**: Different prompts per mode
3. **Activity History**: Track what AI has done in Agent mode
4. **Undo/Redo**: Revert AI changes in Agent mode
5. **Approval Flow**: Require approval for sensitive operations
6. **File-level permissions**: Restrict certain files even in Agent mode

## Testing

To test the implementation:

```bash
cd /workspace/cloud-ide/frontend
npm install
npm run dev
```

Navigate to `http://localhost:3000` and:
1. Verify mode switcher appears in toolbar
2. Click between Ask and Agent modes
3. Open the AI chat panel
4. Send test messages
5. Verify mode indicator updates correctly

## Build

```bash
npm run build # Production build
npm start # Start production server
```

## Architecture Notes

- **State Management**: React hooks (useState) for local state
- **Communication**: Socket.io for real-time backend communication
- **Styling**: Tailwind CSS for responsive design
- **Type Safety**: TypeScript with proper type definitions
- **Component Structure**: Modular, reusable components

## Security Considerations

1. **Backend Validation**: Backend must enforce mode restrictions
2. **Frontend is UI Only**: Mode switching in frontend is for UX, not security
3. **Authentication Required**: All AI operations require valid auth
4. **Rate Limiting**: Prevent abuse of AI endpoints
5. **Audit Logging**: Track all file modifications in Agent mode
190 changes: 190 additions & 0 deletions cloud-ide/frontend/app/components/AIChatPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
'use client'

import { useState, useRef, useEffect } from 'react'
import { AIMode } from './AIModeSwitcher'

interface Message {
id: string
role: 'user' | 'assistant'
content: string
timestamp: Date
}

interface AIChatPanelProps {
mode: AIMode
projectId: string | null
}

export default function AIChatPanel({ mode, projectId }: AIChatPanelProps) {
const [messages, setMessages] = useState<Message[]>([])
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)

useEffect(() => {
// Scroll to bottom when new messages arrive
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])

const handleSendMessage = async () => {
if (!input.trim() || isLoading || !projectId) return

const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: input.trim(),
timestamp: new Date()
}

setMessages(prev => [...prev, userMessage])
setInput('')
setIsLoading(true)

try {
// TODO: Integrate with actual backend API
// For now, simulate a response
await new Promise(resolve => setTimeout(resolve, 1000))

const aiResponse: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: mode === 'ask'
? `I'm in Ask mode, so I can help you understand your code without making changes. What would you like to know about your project?`
: `I'm in Agent mode, so I can help you by reading and modifying your code. What would you like me to do?`,
timestamp: new Date()
}

setMessages(prev => [...prev, aiResponse])
} catch (error) {
console.error('Failed to send message:', error)
} finally {
setIsLoading(false)
inputRef.current?.focus()
}
}

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSendMessage()
}
}

return (
<div className="flex flex-col h-full bg-gray-900">
{/* Header */}
<div className="bg-gray-800 border-b border-gray-700 p-3">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${mode === 'agent' ? 'bg-purple-500' : 'bg-blue-500'}`} />
<h3 className="text-sm font-semibold">
AI Assistant {mode === 'agent' ? '(Agent)' : '(Ask)'}
</h3>
</div>
<p className="text-xs text-gray-400 mt-1">
{mode === 'ask'
? 'Read-only mode: Ask questions about your code'
: 'Full access: Can read and write code'
}
</p>
</div>

{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<div className="flex flex-col items-center justify-center h-full text-gray-500">
<svg className="w-12 h-12 mb-3 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
<p className="text-sm">Start a conversation with the AI</p>
<p className="text-xs mt-1">
{mode === 'ask'
? 'Ask questions about your code'
: 'Request code changes and improvements'
}
</p>
</div>
)}

{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${
message.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-800 text-gray-100 border border-gray-700'
}`}
>
<div className="text-sm whitespace-pre-wrap break-words">
{message.content}
</div>
<div className={`text-xs mt-1 ${
message.role === 'user' ? 'text-blue-200' : 'text-gray-500'
}`}>
{message.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
))}

{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-800 border border-gray-700 rounded-lg p-3">
<div className="flex items-center gap-2">
<div className="flex gap-1">
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
<span className="text-xs text-gray-400">AI is thinking...</span>
</div>
</div>
</div>
)}

<div ref={messagesEndRef} />
</div>

{/* Input */}
<div className="border-t border-gray-700 p-3">
<div className="flex gap-2">
<textarea
ref={inputRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={
mode === 'ask'
? 'Ask a question about your code...'
: 'Describe what you want the AI to do...'
}
className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 resize-none"
rows={2}
disabled={!projectId || isLoading}
/>
<button
onClick={handleSendMessage}
disabled={!input.trim() || isLoading || !projectId}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
!input.trim() || isLoading || !projectId
? 'bg-gray-700 text-gray-500 cursor-not-allowed'
: mode === 'agent'
? 'bg-purple-600 hover:bg-purple-700 text-white'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</button>
</div>
<p className="text-xs text-gray-500 mt-2">
Press Enter to send, Shift+Enter for new line
</p>
</div>
</div>
)
}
Loading