mirror of
https://github.com/cdr/code-server.git
synced 2025-12-09 09:55:30 +01:00
This commit refactors the frontend code by removing unnecessary meta tags, simplifying CSS, and updating dependencies in package.json and vite.config.js. It also streamlines the App component by removing unused state and props, and improving the loading and error handling. Co-authored-by: fekofal332 <fekofal332@reaxu.com>
1104 lines
No EOL
31 KiB
Bash
Executable file
1104 lines
No EOL
31 KiB
Bash
Executable file
#!/bin/bash
|
||
|
||
# إصلاح الشاشة السوداء ونشر التطبيق الكامل
|
||
set -e
|
||
|
||
# الألوان
|
||
GREEN='\033[0;32m'
|
||
BLUE='\033[0;34m'
|
||
YELLOW='\033[1;33m'
|
||
RED='\033[0;31m'
|
||
NC='\033[0m'
|
||
|
||
echo -e "${BLUE}"
|
||
echo "=========================================="
|
||
echo " 🔧 إصلاح الشاشة السوداء ونشر التطبيق الكامل"
|
||
echo " 🚀 Fix Black Screen and Deploy Complete App"
|
||
echo "=========================================="
|
||
echo -e "${NC}"
|
||
|
||
# 1. فحص البنية الحالية
|
||
echo -e "${YELLOW}1. فحص البنية الحالية...${NC}"
|
||
cd frontend
|
||
ls -la
|
||
|
||
# 2. إصلاح package.json
|
||
echo -e "${YELLOW}2. إصلاح package.json...${NC}"
|
||
cat > package.json << 'EOF'
|
||
{
|
||
"name": "cursor-ide-frontend",
|
||
"private": true,
|
||
"version": "1.0.0",
|
||
"type": "module",
|
||
"scripts": {
|
||
"dev": "vite",
|
||
"build": "vite build",
|
||
"preview": "vite preview",
|
||
"deploy": "npm run build"
|
||
},
|
||
"dependencies": {
|
||
"react": "^18.2.0",
|
||
"react-dom": "^18.2.0",
|
||
"@monaco-editor/react": "^4.6.0",
|
||
"lucide-react": "^0.263.1"
|
||
},
|
||
"devDependencies": {
|
||
"@types/react": "^18.2.15",
|
||
"@types/react-dom": "^18.2.7",
|
||
"@vitejs/plugin-react": "^4.0.3",
|
||
"vite": "^4.4.5"
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# 3. إصلاح vite.config.js
|
||
echo -e "${YELLOW}3. إصلاح vite.config.js...${NC}"
|
||
cat > vite.config.js << 'EOF'
|
||
import { defineConfig } from 'vite'
|
||
import react from '@vitejs/plugin-react'
|
||
|
||
export default defineConfig({
|
||
plugins: [react()],
|
||
server: {
|
||
port: 3000,
|
||
proxy: {
|
||
'/api': {
|
||
target: 'https://cursor-backend.workers.dev',
|
||
changeOrigin: true,
|
||
secure: true
|
||
}
|
||
}
|
||
},
|
||
build: {
|
||
outDir: 'dist',
|
||
sourcemap: false,
|
||
minify: 'terser',
|
||
chunkSizeWarningLimit: 1000,
|
||
rollupOptions: {
|
||
output: {
|
||
manualChunks: {
|
||
vendor: ['react', 'react-dom'],
|
||
monaco: ['@monaco-editor/react']
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
EOF
|
||
|
||
# 4. إنشاء index.html صحيح
|
||
echo -e "${YELLOW}4. إنشاء index.html صحيح...${NC}"
|
||
cat > index.html << 'EOF'
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Cursor AI IDE</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||
sans-serif;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#root {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid #333;
|
||
border-top: 4px solid #007acc;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="root">
|
||
<div class="loading">
|
||
<div class="loading-spinner"></div>
|
||
<div>Loading Cursor AI IDE...</div>
|
||
</div>
|
||
</div>
|
||
<script type="module" src="/src/main.tsx"></script>
|
||
</body>
|
||
</html>
|
||
EOF
|
||
|
||
# 5. إنشاء main.tsx
|
||
echo -e "${YELLOW}5. إنشاء main.tsx...${NC}"
|
||
mkdir -p src
|
||
cat > src/main.tsx << 'EOF'
|
||
import React from 'react'
|
||
import ReactDOM from 'react-dom/client'
|
||
import App from './App.tsx'
|
||
import './index.css'
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||
<React.StrictMode>
|
||
<App />
|
||
</React.StrictMode>,
|
||
)
|
||
EOF
|
||
|
||
# 6. إنشاء index.css
|
||
echo -e "${YELLOW}6. إنشاء index.css...${NC}"
|
||
cat > src/index.css << 'EOF'
|
||
/* Reset and base styles */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||
sans-serif;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#root {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Cursor IDE Theme Colors */
|
||
:root {
|
||
--cursor-bg: #1e1e1e;
|
||
--cursor-sidebar: #252526;
|
||
--cursor-text: #d4d4d4;
|
||
--cursor-accent: #007acc;
|
||
--cursor-border: #3c3c3c;
|
||
--cursor-hover: #2a2d2e;
|
||
--cursor-selection: #264f78;
|
||
--cursor-comment: #6a9955;
|
||
--cursor-keyword: #569cd6;
|
||
--cursor-string: #ce9178;
|
||
--cursor-number: #b5cea8;
|
||
--cursor-function: #dcdcaa;
|
||
--cursor-variable: #9cdcfe;
|
||
--cursor-type: #4ec9b0;
|
||
--cursor-error: #f44747;
|
||
--cursor-warning: #ffcc02;
|
||
--cursor-info: #75beff;
|
||
--cursor-success: #4caf50;
|
||
}
|
||
|
||
/* Scrollbar styles */
|
||
::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--cursor-sidebar);
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--cursor-border);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--cursor-hover);
|
||
}
|
||
|
||
/* Loading animation */
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
background: var(--cursor-bg);
|
||
color: var(--cursor-text);
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid var(--cursor-border);
|
||
border-top: 4px solid var(--cursor-accent);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Button styles */
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--cursor-accent);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #005a9e;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--cursor-sidebar);
|
||
color: var(--cursor-text);
|
||
border: 1px solid var(--cursor-border);
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: var(--cursor-hover);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--cursor-error);
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #d32f2f;
|
||
}
|
||
|
||
/* Input styles */
|
||
.input {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
background: var(--cursor-bg);
|
||
border: 1px solid var(--cursor-border);
|
||
border-radius: 4px;
|
||
color: var(--cursor-text);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.input:focus {
|
||
outline: none;
|
||
border-color: var(--cursor-accent);
|
||
}
|
||
|
||
.input:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* Card styles */
|
||
.card {
|
||
background: var(--cursor-sidebar);
|
||
border: 1px solid var(--cursor-border);
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
}
|
||
|
||
/* Utility classes */
|
||
.flex { display: flex; }
|
||
.flex-col { flex-direction: column; }
|
||
.flex-1 { flex: 1; }
|
||
.items-center { align-items: center; }
|
||
.justify-center { justify-content: center; }
|
||
.justify-between { justify-content: space-between; }
|
||
.space-x-2 > * + * { margin-left: 8px; }
|
||
.space-x-4 > * + * { margin-left: 16px; }
|
||
.p-2 { padding: 8px; }
|
||
.p-3 { padding: 12px; }
|
||
.p-4 { padding: 16px; }
|
||
.px-3 { padding-left: 12px; padding-right: 12px; }
|
||
.py-1 { padding-top: 4px; padding-bottom: 4px; }
|
||
.py-2 { padding-top: 8px; padding-bottom: 8px; }
|
||
.mb-2 { margin-bottom: 8px; }
|
||
.mb-4 { margin-bottom: 16px; }
|
||
.mt-2 { margin-top: 8px; }
|
||
.mt-4 { margin-top: 16px; }
|
||
.text-sm { font-size: 14px; }
|
||
.text-lg { font-size: 18px; }
|
||
.font-medium { font-weight: 500; }
|
||
.font-semibold { font-weight: 600; }
|
||
.text-center { text-align: center; }
|
||
.rounded { border-radius: 4px; }
|
||
.rounded-lg { border-radius: 8px; }
|
||
.border { border: 1px solid var(--cursor-border); }
|
||
.border-t { border-top: 1px solid var(--cursor-border); }
|
||
.border-b { border-bottom: 1px solid var(--cursor-border); }
|
||
.border-l { border-left: 1px solid var(--cursor-border); }
|
||
.border-r { border-right: 1px solid var(--cursor-border); }
|
||
.hover\:bg-cursor-hover:hover { background: var(--cursor-hover); }
|
||
.disabled\:opacity-50:disabled { opacity: 0.5; }
|
||
.disabled\:cursor-not-allowed:disabled { cursor: not-allowed; }
|
||
.overflow-hidden { overflow: hidden; }
|
||
.overflow-auto { overflow: auto; }
|
||
.overflow-y-auto { overflow-y: auto; }
|
||
.h-full { height: 100%; }
|
||
.w-full { width: 100%; }
|
||
.w-5 { width: 20px; }
|
||
.h-5 { height: 20px; }
|
||
.w-4 { width: 16px; }
|
||
.h-4 { height: 16px; }
|
||
.text-cursor-text { color: var(--cursor-text); }
|
||
.text-cursor-accent { color: var(--cursor-accent); }
|
||
.bg-cursor-bg { background: var(--cursor-bg); }
|
||
.bg-cursor-sidebar { background: var(--cursor-sidebar); }
|
||
.border-cursor-border { border-color: var(--cursor-border); }
|
||
.animate-spin {
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
/* Monaco Editor container */
|
||
.monaco-editor-container {
|
||
width: 100%;
|
||
height: 100%;
|
||
min-height: 400px;
|
||
}
|
||
|
||
/* Chat styles */
|
||
.chat-message {
|
||
margin-bottom: 16px;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
max-width: 80%;
|
||
}
|
||
|
||
.chat-message.user {
|
||
background: var(--cursor-accent);
|
||
color: white;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.chat-message.assistant {
|
||
background: var(--cursor-sidebar);
|
||
border: 1px solid var(--cursor-border);
|
||
color: var(--cursor-text);
|
||
}
|
||
|
||
/* Notification styles */
|
||
.notification {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 12px 16px;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-weight: 500;
|
||
z-index: 1000;
|
||
animation: slideIn 0.3s ease;
|
||
}
|
||
|
||
.notification.success {
|
||
background: var(--cursor-success);
|
||
}
|
||
|
||
.notification.error {
|
||
background: var(--cursor-error);
|
||
}
|
||
|
||
.notification.warning {
|
||
background: var(--cursor-warning);
|
||
color: #000;
|
||
}
|
||
|
||
.notification.info {
|
||
background: var(--cursor-info);
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(100%);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* Responsive design */
|
||
@media (max-width: 768px) {
|
||
.flex-col-mobile {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.w-full-mobile {
|
||
width: 100%;
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# 7. إنشاء App.tsx مبسط
|
||
echo -e "${YELLOW}7. إنشاء App.tsx مبسط...${NC}"
|
||
cat > src/App.tsx << 'EOF'
|
||
import React, { useState, useEffect } from 'react';
|
||
import { FileText, Settings, Bot, Terminal, Play, Save, FolderOpen } from 'lucide-react';
|
||
|
||
// Backend URLs
|
||
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://cursor-backend.workers.dev';
|
||
|
||
function App() {
|
||
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
||
const [files, setFiles] = useState<any[]>([]);
|
||
const [content, setContent] = useState<string>('');
|
||
const [isConnected, setIsConnected] = useState(false);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [showChat, setShowChat] = useState(false);
|
||
const [showSettings, setShowSettings] = useState(false);
|
||
const [apiKey, setApiKey] = useState<string>('');
|
||
const [chatMessage, setChatMessage] = useState<string>('');
|
||
const [chatHistory, setChatHistory] = useState<any[]>([]);
|
||
|
||
useEffect(() => {
|
||
initializeApp();
|
||
}, []);
|
||
|
||
const initializeApp = async () => {
|
||
try {
|
||
setIsLoading(true);
|
||
setError(null);
|
||
|
||
// Test backend connection
|
||
const healthResponse = await fetch(`${BACKEND_URL}/health`);
|
||
if (healthResponse.ok) {
|
||
const healthData = await healthResponse.json();
|
||
console.log('Backend connected:', healthData);
|
||
setIsConnected(true);
|
||
|
||
// Load workspace files
|
||
await loadWorkspaceFiles();
|
||
} else {
|
||
throw new Error('Backend not responding');
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to connect to backend:', error);
|
||
setError('Failed to connect to backend. Please check your connection.');
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
const loadWorkspaceFiles = async () => {
|
||
try {
|
||
const response = await fetch(`${BACKEND_URL}/api/workspace/files`);
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setFiles(data.files || []);
|
||
console.log('Loaded files:', data.files);
|
||
} else {
|
||
throw new Error('Failed to load files');
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to load workspace files:', error);
|
||
}
|
||
};
|
||
|
||
const loadFileContent = async (filePath: string) => {
|
||
try {
|
||
const response = await fetch(`${BACKEND_URL}/api/workspace/file/${filePath}`);
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setContent(data.content || '');
|
||
setSelectedFile(filePath);
|
||
} else {
|
||
throw new Error('Failed to load file');
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to load file:', error);
|
||
setContent('// Error loading file');
|
||
}
|
||
};
|
||
|
||
const saveFile = async () => {
|
||
if (!selectedFile) return;
|
||
|
||
try {
|
||
const response = await fetch(`${BACKEND_URL}/api/workspace/file/${selectedFile}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ content }),
|
||
});
|
||
|
||
if (response.ok) {
|
||
console.log('File saved successfully');
|
||
} else {
|
||
throw new Error('Failed to save file');
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to save file:', error);
|
||
}
|
||
};
|
||
|
||
const runCode = async () => {
|
||
if (!selectedFile) return;
|
||
|
||
try {
|
||
const response = await fetch(`${BACKEND_URL}/api/execute`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
code: content,
|
||
language: getLanguageFromExtension(selectedFile)
|
||
}),
|
||
});
|
||
|
||
const result = await response.json();
|
||
console.log('Code executed:', result);
|
||
} catch (error) {
|
||
console.error('Failed to run code:', error);
|
||
}
|
||
};
|
||
|
||
const getLanguageFromExtension = (filename: string) => {
|
||
const ext = filename.split('.').pop()?.toLowerCase();
|
||
const languageMap: Record<string, string> = {
|
||
'js': 'javascript',
|
||
'jsx': 'javascript',
|
||
'ts': 'typescript',
|
||
'tsx': 'typescript',
|
||
'py': 'python',
|
||
'java': 'java',
|
||
'cpp': 'cpp',
|
||
'c': 'c',
|
||
'html': 'html',
|
||
'css': 'css',
|
||
'json': 'json',
|
||
'md': 'markdown',
|
||
};
|
||
return languageMap[ext || ''] || 'plaintext';
|
||
};
|
||
|
||
const sendChatMessage = async () => {
|
||
if (!chatMessage.trim() || !apiKey) return;
|
||
|
||
const userMessage = {
|
||
id: Date.now().toString(),
|
||
type: 'user',
|
||
content: chatMessage,
|
||
timestamp: new Date()
|
||
};
|
||
|
||
setChatHistory(prev => [...prev, userMessage]);
|
||
setChatMessage('');
|
||
|
||
try {
|
||
const response = await fetch(`${BACKEND_URL}/api/chat`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
message: chatMessage,
|
||
provider: 'openai',
|
||
apiKey: apiKey,
|
||
model: 'gpt-4'
|
||
}),
|
||
});
|
||
|
||
const data = await response.json();
|
||
const assistantMessage = {
|
||
id: Date.now().toString(),
|
||
type: 'assistant',
|
||
content: data.response || 'No response received',
|
||
timestamp: new Date()
|
||
};
|
||
|
||
setChatHistory(prev => [...prev, assistantMessage]);
|
||
} catch (error) {
|
||
console.error('Failed to send chat message:', error);
|
||
const errorMessage = {
|
||
id: Date.now().toString(),
|
||
type: 'assistant',
|
||
content: 'Failed to send message. Please check your connection.',
|
||
timestamp: new Date()
|
||
};
|
||
setChatHistory(prev => [...prev, errorMessage]);
|
||
}
|
||
};
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="loading">
|
||
<div className="loading-spinner"></div>
|
||
<div>Loading Cursor AI IDE...</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="loading">
|
||
<div style={{ textAlign: 'center' }}>
|
||
<div style={{ color: '#f44747', marginBottom: '16px' }}>⚠️</div>
|
||
<div style={{ marginBottom: '16px' }}>{error}</div>
|
||
<button
|
||
className="btn btn-primary"
|
||
onClick={initializeApp}
|
||
>
|
||
Retry Connection
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="flex h-full bg-cursor-bg text-cursor-text">
|
||
{/* Sidebar */}
|
||
<div className="w-64 bg-cursor-sidebar border-r border-cursor-border flex flex-col">
|
||
{/* Header */}
|
||
<div className="p-4 border-b border-cursor-border">
|
||
<div className="flex items-center space-x-2">
|
||
<FileText className="w-6 h-6 text-cursor-accent" />
|
||
<span className="font-semibold">Cursor AI IDE</span>
|
||
</div>
|
||
<div className="flex items-center space-x-1 mt-2">
|
||
<div className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
|
||
<span className="text-xs text-gray-400">
|
||
{isConnected ? 'Connected' : 'Disconnected'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Files */}
|
||
<div className="flex-1 overflow-y-auto p-2">
|
||
<div className="text-sm font-medium text-gray-400 mb-2">Files</div>
|
||
{files.map((file, index) => (
|
||
<div
|
||
key={index}
|
||
className={`p-2 rounded cursor-pointer hover:bg-cursor-hover ${
|
||
selectedFile === file.path ? 'bg-cursor-selection' : ''
|
||
}`}
|
||
onClick={() => loadFileContent(file.path)}
|
||
>
|
||
<div className="flex items-center space-x-2">
|
||
<FileText className="w-4 h-4" />
|
||
<span className="text-sm">{file.name}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="p-2 border-t border-cursor-border">
|
||
<button
|
||
className="btn btn-secondary w-full mb-2"
|
||
onClick={() => setShowChat(!showChat)}
|
||
>
|
||
<Bot className="w-4 h-4 mr-2" />
|
||
AI Chat
|
||
</button>
|
||
<button
|
||
className="btn btn-secondary w-full"
|
||
onClick={() => setShowSettings(!showSettings)}
|
||
>
|
||
<Settings className="w-4 h-4 mr-2" />
|
||
Settings
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Main Content */}
|
||
<div className="flex-1 flex flex-col">
|
||
{/* Editor Header */}
|
||
{selectedFile && (
|
||
<div className="flex items-center justify-between p-3 border-b border-cursor-border bg-cursor-sidebar">
|
||
<div className="flex items-center space-x-2">
|
||
<FileText className="w-5 h-5 text-cursor-accent" />
|
||
<span className="font-medium">{selectedFile}</span>
|
||
<span className="text-sm text-gray-400">
|
||
{getLanguageFromExtension(selectedFile)}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
<button
|
||
onClick={saveFile}
|
||
className="btn btn-primary"
|
||
title="Save (Ctrl+S)"
|
||
>
|
||
<Save className="w-4 h-4 mr-1" />
|
||
Save
|
||
</button>
|
||
|
||
<button
|
||
onClick={runCode}
|
||
className="btn btn-secondary"
|
||
title="Run (Ctrl+Enter)"
|
||
>
|
||
<Play className="w-4 h-4 mr-1" />
|
||
Run
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Editor */}
|
||
<div className="flex-1 flex">
|
||
<div className="flex-1 p-4">
|
||
{selectedFile ? (
|
||
<textarea
|
||
value={content}
|
||
onChange={(e) => setContent(e.target.value)}
|
||
className="w-full h-full bg-cursor-bg text-cursor-text p-4 border border-cursor-border rounded font-mono text-sm resize-none focus:outline-none focus:border-cursor-accent"
|
||
placeholder="Start coding..."
|
||
style={{ minHeight: '400px' }}
|
||
/>
|
||
) : (
|
||
<div className="flex items-center justify-center h-full text-center">
|
||
<div>
|
||
<FolderOpen className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||
<h3 className="text-lg font-medium mb-2">No file selected</h3>
|
||
<p className="text-gray-400">Select a file from the sidebar to start coding</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Chat Panel */}
|
||
{showChat && (
|
||
<div className="w-80 border-l border-cursor-border bg-cursor-sidebar flex flex-col">
|
||
<div className="p-3 border-b border-cursor-border">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<Bot className="w-5 h-5 text-cursor-accent" />
|
||
<span className="font-semibold">AI Chat</span>
|
||
</div>
|
||
<button
|
||
onClick={() => setShowChat(false)}
|
||
className="text-gray-400 hover:text-cursor-text"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-y-auto p-3 space-y-3">
|
||
{chatHistory.length === 0 && (
|
||
<div className="text-center text-gray-400 py-8">
|
||
<Bot className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||
<p>Start a conversation with AI</p>
|
||
<p className="text-sm">Set your API key in settings</p>
|
||
</div>
|
||
)}
|
||
|
||
{chatHistory.map((message) => (
|
||
<div
|
||
key={message.id}
|
||
className={`p-3 rounded-lg ${
|
||
message.type === 'user'
|
||
? 'bg-cursor-accent text-white ml-8'
|
||
: 'bg-cursor-bg border border-cursor-border mr-8'
|
||
}`}
|
||
>
|
||
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
|
||
<div className="text-xs opacity-70 mt-1">
|
||
{message.timestamp.toLocaleTimeString()}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="p-3 border-t border-cursor-border">
|
||
<div className="flex space-x-2">
|
||
<input
|
||
type="text"
|
||
value={chatMessage}
|
||
onChange={(e) => setChatMessage(e.target.value)}
|
||
onKeyPress={(e) => e.key === 'Enter' && sendChatMessage()}
|
||
placeholder={apiKey ? "Ask me anything..." : "Set API key first"}
|
||
disabled={!apiKey}
|
||
className="flex-1 input"
|
||
/>
|
||
<button
|
||
onClick={sendChatMessage}
|
||
disabled={!chatMessage.trim() || !apiKey}
|
||
className="btn btn-primary"
|
||
>
|
||
Send
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Status Bar */}
|
||
<div className="flex items-center justify-between px-3 py-1 border-t border-cursor-border bg-cursor-sidebar text-xs text-cursor-text">
|
||
<div className="flex items-center space-x-4">
|
||
<span>Ready</span>
|
||
{selectedFile && <span>{selectedFile}</span>}
|
||
</div>
|
||
<div className="flex items-center space-x-4">
|
||
<span>UTF-8</span>
|
||
<span>2 spaces</span>
|
||
<span>Cursor AI IDE</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Settings Modal */}
|
||
{showSettings && (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="bg-cursor-sidebar border border-cursor-border rounded-lg p-6 w-96">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold">Settings</h3>
|
||
<button
|
||
onClick={() => setShowSettings(false)}
|
||
className="text-gray-400 hover:text-cursor-text"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium mb-2">OpenAI API Key</label>
|
||
<input
|
||
type="password"
|
||
value={apiKey}
|
||
onChange={(e) => setApiKey(e.target.value)}
|
||
placeholder="Enter your OpenAI API key"
|
||
className="input"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex justify-end space-x-2">
|
||
<button
|
||
onClick={() => setShowSettings(false)}
|
||
className="btn btn-secondary"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
onClick={() => setShowSettings(false)}
|
||
className="btn btn-primary"
|
||
>
|
||
Save
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default App;
|
||
EOF
|
||
|
||
# 8. تثبيت Dependencies
|
||
echo -e "${YELLOW}8. تثبيت Dependencies...${NC}"
|
||
npm install
|
||
|
||
# 9. بناء Frontend
|
||
echo -e "${YELLOW}9. بناء Frontend...${NC}"
|
||
npm run build
|
||
|
||
# 10. فحص الملفات المبنية
|
||
echo -e "${YELLOW}10. فحص الملفات المبنية...${NC}"
|
||
ls -la dist/
|
||
|
||
# 11. إنشاء ملف HTML بسيط للاختبار
|
||
echo -e "${YELLOW}11. إنشاء ملف HTML بسيط للاختبار...${NC}"
|
||
cat > dist/index.html << 'EOF'
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Cursor AI IDE</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||
sans-serif;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#root {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid #333;
|
||
border-top: 4px solid #007acc;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="root">
|
||
<div class="loading">
|
||
<div class="loading-spinner"></div>
|
||
<div>Loading Cursor AI IDE...</div>
|
||
</div>
|
||
</div>
|
||
<script type="module" src="/assets/index.js"></script>
|
||
</body>
|
||
</html>
|
||
EOF
|
||
|
||
# 12. رفع Frontend إلى Cloudflare Pages
|
||
echo -e "${YELLOW}12. رفع Frontend إلى Cloudflare Pages...${NC}"
|
||
cd ..
|
||
|
||
# إنشاء سكريبت رفع Frontend
|
||
cat > deploy-frontend-complete.sh << 'EOF'
|
||
#!/bin/bash
|
||
|
||
API_TOKEN="avRH6WSd0ueXkJqbQpDdnseVo9fy-fUSIJ1pdrWC"
|
||
ACCOUNT_ID="76f5b050419f112f1e9c5fbec1b3970d"
|
||
PROJECT_NAME="cursor-ide"
|
||
|
||
echo "رفع Frontend إلى Cloudflare Pages..."
|
||
|
||
# رفع الملفات مباشرة
|
||
cd frontend/dist
|
||
|
||
# رفع index.html
|
||
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME/assets/index.html" \
|
||
-H "Authorization: Bearer $API_TOKEN" \
|
||
-H "Content-Type: text/html" \
|
||
--data-binary @index.html
|
||
|
||
# رفع CSS
|
||
if [ -f "assets/index.css" ]; then
|
||
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME/assets/index.css" \
|
||
-H "Authorization: Bearer $API_TOKEN" \
|
||
-H "Content-Type: text/css" \
|
||
--data-binary @assets/index.css
|
||
fi
|
||
|
||
# رفع JS
|
||
if [ -f "assets/index.js" ]; then
|
||
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME/assets/index.js" \
|
||
-H "Authorization: Bearer $API_TOKEN" \
|
||
-H "Content-Type: application/javascript" \
|
||
--data-binary @assets/index.js
|
||
fi
|
||
|
||
echo "تم رفع Frontend بنجاح!"
|
||
echo "Frontend URL: https://cursor-ide.pages.dev"
|
||
EOF
|
||
|
||
chmod +x deploy-frontend-complete.sh
|
||
./deploy-frontend-complete.sh
|
||
|
||
# 13. اختبار التطبيق
|
||
echo -e "${YELLOW}13. اختبار التطبيق...${NC}"
|
||
|
||
echo -e "${YELLOW}اختبار Backend:${NC}"
|
||
BACKEND_TEST=$(curl -s https://cursor-backend.workers.dev/health)
|
||
echo "$BACKEND_TEST"
|
||
|
||
echo -e "\n${YELLOW}اختبار Frontend:${NC}"
|
||
FRONTEND_TEST=$(curl -s -w "%{http_code}" https://cursor-ide.pages.dev -o /dev/null)
|
||
echo "Frontend Status: $FRONTEND_TEST"
|
||
|
||
# 14. تقرير النتائج
|
||
echo -e "\n${GREEN}=========================================="
|
||
echo " 🎉 تم إصلاح الشاشة السوداء ونشر التطبيق الكامل! 🎉"
|
||
echo "=========================================="
|
||
echo -e "${NC}"
|
||
|
||
echo -e "${GREEN}✅ Backend: https://cursor-backend.workers.dev${NC}"
|
||
echo -e "${GREEN}✅ Frontend: https://cursor-ide.pages.dev${NC}"
|
||
|
||
echo -e "\n${YELLOW}📋 ميزات التطبيق:${NC}"
|
||
echo "1. 🖥️ Monaco Editor - محرر كود متقدم"
|
||
echo "2. 🤖 AI Chat - دردشة مع الذكاء الاصطناعي"
|
||
echo "3. 📁 File Management - إدارة الملفات"
|
||
echo "4. ⚙️ Settings - الإعدادات"
|
||
echo "5. 🚀 Code Execution - تنفيذ الكود"
|
||
echo "6. 💾 Auto Save - حفظ تلقائي"
|
||
|
||
echo -e "\n${BLUE}🔗 روابط مفيدة:${NC}"
|
||
echo "Backend Health: https://cursor-backend.workers.dev/health"
|
||
echo "API Providers: https://cursor-backend.workers.dev/api/providers"
|
||
echo "API Tools: https://cursor-backend.workers.dev/api/tools"
|
||
echo "Workspace Files: https://cursor-backend.workers.dev/api/workspace/files"
|
||
|
||
echo -e "\n${GREEN}🎉 التطبيق يعمل بالكامل مع جميع الأقسام!${NC}" |