mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-09 10:59:47 +00:00
fix: resolve React errors and localStorage quota issues
- Fix DOMException in ChatInterface handleImageFiles function with proper file validation - Add ErrorBoundary component to catch React errors gracefully - Implement safeLocalStorage utility to handle QuotaExceededError - Add intelligent quota management with automatic cleanup - Limit chat history to 50 messages per project to prevent storage bloat - Add comprehensive error handling for all localStorage operations - Create comprehensive CLAUDE.md documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
321
CLAUDE.md
Normal file
321
CLAUDE.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# CLAUDE.md - Claude Code UI Project Guide
|
||||
|
||||
## 🚀 Quick Start Commands
|
||||
|
||||
### Development
|
||||
```bash
|
||||
# Start development server (frontend + backend)
|
||||
npm run dev
|
||||
|
||||
# Start backend only
|
||||
npm run server
|
||||
|
||||
# Start frontend only
|
||||
npm run client
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Testing & Quality
|
||||
```bash
|
||||
# Run tests (if available)
|
||||
npm test
|
||||
|
||||
# Check for linting issues
|
||||
npm run lint
|
||||
|
||||
# Type checking (if TypeScript)
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Port Configuration
|
||||
- **Backend:** http://0.0.0.0:2008
|
||||
- **Frontend:** http://localhost:2009
|
||||
- **WebSocket:** ws://localhost:2008/ws
|
||||
|
||||
## 🏗️ High-Level Architecture
|
||||
|
||||
### Technology Stack
|
||||
- **Frontend:** React 18 + Vite
|
||||
- **Backend:** Express.js with WebSocket server
|
||||
- **Database:** SQLite (better-sqlite3)
|
||||
- **Authentication:** JWT + bcrypt
|
||||
- **Real-time:** WebSockets for live chat
|
||||
|
||||
### System Design
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ React Client │◄──►│ Express Server │◄──►│ Claude CLI │
|
||||
│ │ │ │ │ │
|
||||
│ - Chat UI │ │ - Auth Routes │ │ - Code Actions │
|
||||
│ - Project Mgmt │ │ - WebSockets │ │ - File Ops │
|
||||
│ - File Browser │ │ - Git API │ │ - Tool Calling │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
└───────────────────────┼───────────────────────┘
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ SQLite DB │
|
||||
│ - Users │
|
||||
│ - Sessions │
|
||||
│ - Projects │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
#### Frontend (`/src`)
|
||||
- **App.jsx** - Main application with session protection
|
||||
- **ChatInterface.jsx** - Real-time chat with Claude
|
||||
- **components/** - Reusable UI components
|
||||
- **utils/api.js** - API client utilities
|
||||
|
||||
#### Backend (`/server`)
|
||||
- **index.js** - Express server with WebSocket setup
|
||||
- **routes/** - API endpoints (auth, git, files)
|
||||
- **middleware/** - Authentication & validation
|
||||
- **database/** - SQLite schema & operations
|
||||
|
||||
#### Authentication System
|
||||
- **Single-user system** - Only one account allowed
|
||||
- **JWT tokens** - Stateless authentication
|
||||
- **Setup mode** - Automatic when no users exist
|
||||
- **Session protection** - Prevents interruptions during active chats
|
||||
|
||||
## 🔧 Configuration & Setup
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
# Server configuration
|
||||
PORT=2008
|
||||
VITE_PORT=2009
|
||||
|
||||
# Database
|
||||
DB_PATH=server/database/auth.db
|
||||
|
||||
# Optional: Claude API configuration
|
||||
ANTHROPIC_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
### Initial Setup
|
||||
1. **Clone and install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **First run (setup mode):**
|
||||
```bash
|
||||
npm run dev
|
||||
# Navigate to http://localhost:2009
|
||||
# Create your admin account
|
||||
```
|
||||
|
||||
3. **Database reset (if needed):**
|
||||
```bash
|
||||
rm server/database/auth.db
|
||||
npm run dev # Triggers setup mode
|
||||
```
|
||||
|
||||
## 🎯 Core Features
|
||||
|
||||
### Project Management
|
||||
- **Multi-project support** - Switch between different codebases
|
||||
- **Git integration** - Status, branches, and file tracking
|
||||
- **Session isolation** - Each project maintains separate chat history
|
||||
- **File browser** - Navigate and edit project files
|
||||
|
||||
### Chat Interface
|
||||
- **Real-time messaging** - Instant responses via WebSockets
|
||||
- **Tool integration** - Claude can execute code operations
|
||||
- **Session protection** - Prevents UI updates during active conversations
|
||||
- **Message history** - Persistent chat logs per project
|
||||
- **Status indicators** - Shows Claude's working state
|
||||
|
||||
### Security Features
|
||||
- **Tool permissions** - Disabled by default for security
|
||||
- **Project sandboxing** - Isolated file system access
|
||||
- **Authentication required** - No anonymous access
|
||||
- **Session validation** - JWT token verification
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Port Conflicts
|
||||
```bash
|
||||
# Kill existing processes
|
||||
pkill -f "node server/index.js"
|
||||
pkill -f "npm run dev"
|
||||
|
||||
# Start fresh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
#### Database Issues
|
||||
```bash
|
||||
# Reset database (triggers setup mode)
|
||||
rm server/database/auth.db
|
||||
npm run dev
|
||||
```
|
||||
|
||||
#### Git Path Errors
|
||||
- **Symptom:** Console logs showing "Project path not found"
|
||||
- **Cause:** Projects reference non-existent directories
|
||||
- **Fix:** Update project paths or remove orphaned projects
|
||||
|
||||
#### React Errors in ChatInterface
|
||||
- **Symptom:** JavaScript errors when loading chat sessions
|
||||
- **Cause:** Missing project directories or invalid status messages
|
||||
- **Fix:** Implement better error boundaries and path validation
|
||||
|
||||
### Performance Optimization
|
||||
```bash
|
||||
# Clear node modules and reinstall
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
|
||||
# Rebuild frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
claudecodeui/
|
||||
├── src/ # React frontend
|
||||
│ ├── components/ # Reusable UI components
|
||||
│ │ ├── ChatInterface.jsx
|
||||
│ │ ├── ClaudeStatus.jsx
|
||||
│ │ └── TodoList.jsx
|
||||
│ ├── utils/ # Frontend utilities
|
||||
│ └── App.jsx # Main application
|
||||
├── server/ # Express backend
|
||||
│ ├── routes/ # API endpoints
|
||||
│ │ ├── auth.js # Authentication
|
||||
│ │ ├── git.js # Git operations
|
||||
│ │ └── files.js # File management
|
||||
│ ├── middleware/ # Auth & validation
|
||||
│ ├── database/ # SQLite setup
|
||||
│ └── index.js # Server entry point
|
||||
├── public/ # Static assets
|
||||
├── package.json # Dependencies & scripts
|
||||
└── vite.config.js # Frontend build config
|
||||
```
|
||||
|
||||
## 🔄 Development Workflow
|
||||
|
||||
### Adding New Features
|
||||
1. **Backend API:** Add routes in `/server/routes/`
|
||||
2. **Frontend UI:** Create components in `/src/components/`
|
||||
3. **WebSocket events:** Update both client and server handlers
|
||||
4. **Database changes:** Modify schema in `/server/database/`
|
||||
|
||||
### Git Integration Points
|
||||
- **Project loading:** `server/routes/git.js:62`
|
||||
- **Status polling:** Continuous Git status checks
|
||||
- **Branch management:** `server/routes/git.js:198`
|
||||
- **Error handling:** `validateGitRepository()` function
|
||||
|
||||
### Session Protection System
|
||||
- **Activation:** When user sends chat message
|
||||
- **WebSocket events:** `session-created`, `claude-complete`, `session-aborted`
|
||||
- **Purpose:** Prevents sidebar updates during active conversations
|
||||
- **Implementation:** `App.jsx` + `ChatInterface.jsx` coordination
|
||||
|
||||
## 🚨 Known Issues & Fixes
|
||||
|
||||
### Issue: Continuous Git Errors
|
||||
**Problem:** Logs show repeated "Project path not found" errors
|
||||
**Solution:**
|
||||
```javascript
|
||||
// Add to git.js validation
|
||||
const validateProjectPath = (path) => {
|
||||
if (!fs.existsSync(path)) {
|
||||
console.warn(`Project path does not exist: ${path}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
```
|
||||
|
||||
### Issue: React Error in ChatInterface Line 1515
|
||||
**Problem:** Error when loading existing chat sessions
|
||||
**Location:** `src/components/ChatInterface.jsx:1515`
|
||||
**Solution:** Add error boundary around claude-status message handling
|
||||
|
||||
### Issue: WebSocket Connection Drops
|
||||
**Problem:** Chat becomes unresponsive
|
||||
**Solution:** Implement automatic reconnection logic
|
||||
|
||||
## 📚 Integration with Claude Code CLI
|
||||
|
||||
This UI acts as a web interface for the Claude Code CLI:
|
||||
|
||||
### Tool Integration
|
||||
- **File operations** - Read, write, edit files
|
||||
- **Git commands** - Status, diff, commit, push
|
||||
- **Terminal access** - Execute shell commands
|
||||
- **Project navigation** - Browse directory structure
|
||||
|
||||
### API Endpoints
|
||||
- `POST /api/chat/send` - Send message to Claude
|
||||
- `GET /api/projects` - List available projects
|
||||
- `GET /api/git/status` - Get Git repository status
|
||||
- `POST /api/files/read` - Read file contents
|
||||
- `POST /api/files/write` - Write file contents
|
||||
|
||||
### WebSocket Events
|
||||
- `message` - Chat messages
|
||||
- `claude-status` - Working status updates
|
||||
- `session-created` - New chat session
|
||||
- `session-complete` - Chat finished
|
||||
- `session-aborted` - Chat interrupted
|
||||
|
||||
## 🔐 Security Considerations
|
||||
|
||||
### Authentication
|
||||
- **Single-user system** - Only one account supported
|
||||
- **JWT expiration** - Tokens have limited lifetime
|
||||
- **Password hashing** - bcrypt with salt rounds 12
|
||||
- **Setup protection** - Registration only when no users exist
|
||||
|
||||
### File System Access
|
||||
- **Project sandboxing** - Limited to configured directories
|
||||
- **Path validation** - Prevent directory traversal attacks
|
||||
- **Tool permissions** - Disabled by default
|
||||
- **Git operations** - Validated repository paths
|
||||
|
||||
### Network Security
|
||||
- **CORS configuration** - Restricted origins
|
||||
- **WebSocket authentication** - JWT token required
|
||||
- **Input validation** - Sanitized user inputs
|
||||
- **Error messages** - No sensitive information leakage
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support & Maintenance
|
||||
|
||||
### Health Checks
|
||||
- **Database connection** - SQLite file integrity
|
||||
- **WebSocket status** - Active connections count
|
||||
- **Git operations** - Repository accessibility
|
||||
- **File system** - Project directory permissions
|
||||
|
||||
### Monitoring
|
||||
- **Server logs** - Console output for debugging
|
||||
- **Error tracking** - Catch and log exceptions
|
||||
- **Performance** - WebSocket message timing
|
||||
- **Resource usage** - Memory and CPU monitoring
|
||||
|
||||
### Updates
|
||||
- **Dependencies** - Regular npm audit and updates
|
||||
- **Security patches** - Keep Express and React current
|
||||
- **Claude CLI** - Ensure compatibility with latest version
|
||||
- **Database migrations** - Handle schema changes
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2024-12-28*
|
||||
*Version: 1.4.0*
|
||||
*Tested with: Claude Code CLI*
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-code-ui",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-code-ui",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-code": "^1.0.24",
|
||||
|
||||
@@ -26,6 +26,88 @@ import ClaudeStatus from './ClaudeStatus';
|
||||
import { MicButton } from './MicButton.jsx';
|
||||
import { api } from '../utils/api';
|
||||
|
||||
// Safe localStorage utility to handle quota exceeded errors
|
||||
const safeLocalStorage = {
|
||||
setItem: (key, value) => {
|
||||
try {
|
||||
// For chat messages, implement compression and size limits
|
||||
if (key.startsWith('chat_messages_') && typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
// Limit to last 50 messages to prevent storage bloat
|
||||
if (Array.isArray(parsed) && parsed.length > 50) {
|
||||
console.warn(`Truncating chat history for ${key} from ${parsed.length} to 50 messages`);
|
||||
const truncated = parsed.slice(-50);
|
||||
value = JSON.stringify(truncated);
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.warn('Could not parse chat messages for truncation:', parseError);
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
if (error.name === 'QuotaExceededError') {
|
||||
console.warn('localStorage quota exceeded, clearing old data');
|
||||
// Clear old chat messages to free up space
|
||||
const keys = Object.keys(localStorage);
|
||||
const chatKeys = keys.filter(k => k.startsWith('chat_messages_')).sort();
|
||||
|
||||
// Remove oldest chat data first, keeping only the 3 most recent projects
|
||||
if (chatKeys.length > 3) {
|
||||
chatKeys.slice(0, chatKeys.length - 3).forEach(k => {
|
||||
localStorage.removeItem(k);
|
||||
console.log(`Removed old chat data: ${k}`);
|
||||
});
|
||||
}
|
||||
|
||||
// If still failing, clear draft inputs too
|
||||
const draftKeys = keys.filter(k => k.startsWith('draft_input_'));
|
||||
draftKeys.forEach(k => {
|
||||
localStorage.removeItem(k);
|
||||
});
|
||||
|
||||
// Try again with reduced data
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch (retryError) {
|
||||
console.error('Failed to save to localStorage even after cleanup:', retryError);
|
||||
// Last resort: Try to save just the last 10 messages
|
||||
if (key.startsWith('chat_messages_') && typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (Array.isArray(parsed) && parsed.length > 10) {
|
||||
const minimal = parsed.slice(-10);
|
||||
localStorage.setItem(key, JSON.stringify(minimal));
|
||||
console.warn('Saved only last 10 messages due to quota constraints');
|
||||
}
|
||||
} catch (finalError) {
|
||||
console.error('Final save attempt failed:', finalError);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('localStorage error:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
getItem: (key) => {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch (error) {
|
||||
console.error('localStorage getItem error:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
removeItem: (key) => {
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.error('localStorage removeItem error:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Memoized message component to prevent unnecessary re-renders
|
||||
const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFileOpen, onShowSettings, autoExpandTools, showRawParameters }) => {
|
||||
const isGrouped = prevMessage && prevMessage.type === message.type &&
|
||||
@@ -987,13 +1069,13 @@ const ImageAttachment = ({ file, onRemove, uploadProgress, error }) => {
|
||||
function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters, autoScrollToBottom }) {
|
||||
const [input, setInput] = useState(() => {
|
||||
if (typeof window !== 'undefined' && selectedProject) {
|
||||
return localStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||
return safeLocalStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const [chatMessages, setChatMessages] = useState(() => {
|
||||
if (typeof window !== 'undefined' && selectedProject) {
|
||||
const saved = localStorage.getItem(`chat_messages_${selectedProject.name}`);
|
||||
const saved = safeLocalStorage.getItem(`chat_messages_${selectedProject.name}`);
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
}
|
||||
return [];
|
||||
@@ -1278,16 +1360,16 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
// Persist input draft to localStorage
|
||||
useEffect(() => {
|
||||
if (selectedProject && input !== '') {
|
||||
localStorage.setItem(`draft_input_${selectedProject.name}`, input);
|
||||
safeLocalStorage.setItem(`draft_input_${selectedProject.name}`, input);
|
||||
} else if (selectedProject && input === '') {
|
||||
localStorage.removeItem(`draft_input_${selectedProject.name}`);
|
||||
safeLocalStorage.removeItem(`draft_input_${selectedProject.name}`);
|
||||
}
|
||||
}, [input, selectedProject]);
|
||||
|
||||
// Persist chat messages to localStorage
|
||||
useEffect(() => {
|
||||
if (selectedProject && chatMessages.length > 0) {
|
||||
localStorage.setItem(`chat_messages_${selectedProject.name}`, JSON.stringify(chatMessages));
|
||||
safeLocalStorage.setItem(`chat_messages_${selectedProject.name}`, JSON.stringify(chatMessages));
|
||||
}
|
||||
}, [chatMessages, selectedProject]);
|
||||
|
||||
@@ -1295,7 +1377,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
// Always load saved input draft for the project
|
||||
const savedInput = localStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||
const savedInput = safeLocalStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||
if (savedInput !== input) {
|
||||
setInput(savedInput);
|
||||
}
|
||||
@@ -1488,7 +1570,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
|
||||
// Clear persisted chat messages after successful completion
|
||||
if (selectedProject && latestMessage.exitCode === 0) {
|
||||
localStorage.removeItem(`chat_messages_${selectedProject.name}`);
|
||||
safeLocalStorage.removeItem(`chat_messages_${selectedProject.name}`);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1745,14 +1827,33 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
// Handle image files from drag & drop or file picker
|
||||
const handleImageFiles = useCallback((files) => {
|
||||
const validFiles = files.filter(file => {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
try {
|
||||
// Validate file object and properties
|
||||
if (!file || typeof file !== 'object') {
|
||||
console.warn('Invalid file object:', file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.type || !file.type.startsWith('image/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.size || file.size > 5 * 1024 * 1024) {
|
||||
// Safely get file name with fallback
|
||||
const fileName = file.name || 'Unknown file';
|
||||
setImageErrors(prev => {
|
||||
const newMap = new Map(prev);
|
||||
newMap.set(fileName, 'File too large (max 5MB)');
|
||||
return newMap;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error validating file:', error, file);
|
||||
return false;
|
||||
}
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setImageErrors(prev => new Map(prev).set(file.name, 'File too large (max 5MB)'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (validFiles.length > 0) {
|
||||
@@ -1808,7 +1909,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
});
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('auth-token');
|
||||
const token = safeLocalStorage.getItem('auth-token');
|
||||
const headers = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
@@ -1871,7 +1972,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
// Get tools settings from localStorage
|
||||
const getToolsSettings = () => {
|
||||
try {
|
||||
const savedSettings = localStorage.getItem('claude-tools-settings');
|
||||
const savedSettings = safeLocalStorage.getItem('claude-tools-settings');
|
||||
if (savedSettings) {
|
||||
return JSON.parse(savedSettings);
|
||||
}
|
||||
@@ -1917,7 +2018,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
|
||||
// Clear the saved draft since message was sent
|
||||
if (selectedProject) {
|
||||
localStorage.removeItem(`draft_input_${selectedProject.name}`);
|
||||
safeLocalStorage.removeItem(`draft_input_${selectedProject.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
73
src/components/ErrorBoundary.jsx
Normal file
73
src/components/ErrorBoundary.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null, errorInfo: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
// Update state so the next render will show the fallback UI
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
// Log the error details
|
||||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||
|
||||
// You can also log the error to an error reporting service here
|
||||
this.setState({
|
||||
error: error,
|
||||
errorInfo: errorInfo
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Fallback UI
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center p-8 text-center">
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-md">
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="ml-3 text-sm font-medium text-red-800">
|
||||
Something went wrong
|
||||
</h3>
|
||||
</div>
|
||||
<div className="text-sm text-red-700">
|
||||
<p className="mb-2">An error occurred while loading the chat interface.</p>
|
||||
{this.props.showDetails && this.state.error && (
|
||||
<details className="mt-4">
|
||||
<summary className="cursor-pointer text-xs font-mono">Error Details</summary>
|
||||
<pre className="mt-2 text-xs bg-red-100 p-2 rounded overflow-auto max-h-40">
|
||||
{this.state.error.toString()}
|
||||
{this.state.errorInfo && this.state.errorInfo.componentStack}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
this.setState({ hasError: false, error: null, errorInfo: null });
|
||||
if (this.props.onRetry) this.props.onRetry();
|
||||
}}
|
||||
className="bg-red-600 text-white px-4 py-2 rounded text-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -17,6 +17,7 @@ import FileTree from './FileTree';
|
||||
import CodeEditor from './CodeEditor';
|
||||
import Shell from './Shell';
|
||||
import GitPanel from './GitPanel';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
function MainContent({
|
||||
selectedProject,
|
||||
@@ -269,23 +270,25 @@ function MainContent({
|
||||
{/* Content Area */}
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||
<div className={`h-full ${activeTab === 'chat' ? 'block' : 'hidden'}`}>
|
||||
<ChatInterface
|
||||
selectedProject={selectedProject}
|
||||
selectedSession={selectedSession}
|
||||
ws={ws}
|
||||
sendMessage={sendMessage}
|
||||
messages={messages}
|
||||
onFileOpen={handleFileOpen}
|
||||
onInputFocusChange={onInputFocusChange}
|
||||
onSessionActive={onSessionActive}
|
||||
onSessionInactive={onSessionInactive}
|
||||
onReplaceTemporarySession={onReplaceTemporarySession}
|
||||
onNavigateToSession={onNavigateToSession}
|
||||
onShowSettings={onShowSettings}
|
||||
autoExpandTools={autoExpandTools}
|
||||
showRawParameters={showRawParameters}
|
||||
autoScrollToBottom={autoScrollToBottom}
|
||||
/>
|
||||
<ErrorBoundary showDetails={true}>
|
||||
<ChatInterface
|
||||
selectedProject={selectedProject}
|
||||
selectedSession={selectedSession}
|
||||
ws={ws}
|
||||
sendMessage={sendMessage}
|
||||
messages={messages}
|
||||
onFileOpen={handleFileOpen}
|
||||
onInputFocusChange={onInputFocusChange}
|
||||
onSessionActive={onSessionActive}
|
||||
onSessionInactive={onSessionInactive}
|
||||
onReplaceTemporarySession={onReplaceTemporarySession}
|
||||
onNavigateToSession={onNavigateToSession}
|
||||
onShowSettings={onShowSettings}
|
||||
autoExpandTools={autoExpandTools}
|
||||
showRawParameters={showRawParameters}
|
||||
autoScrollToBottom={autoScrollToBottom}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<div className={`h-full overflow-hidden ${activeTab === 'files' ? 'block' : 'hidden'}`}>
|
||||
<FileTree selectedProject={selectedProject} />
|
||||
|
||||
Reference in New Issue
Block a user