diff --git a/cursor-fullstack/docker-compose.yml b/cursor-fullstack/docker-compose.yml new file mode 100644 index 000000000..6a96adf88 --- /dev/null +++ b/cursor-fullstack/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3.8' + +services: + backend: + build: + context: ./packages/backend/claudable + dockerfile: Dockerfile + ports: + - "3001:3001" + - "8080:8080" + environment: + - NODE_ENV=production + - PORT=3001 + - WS_PORT=8080 + volumes: + - ./workspace:/app/workspace + networks: + - cursor-network + + frontend: + build: + context: ./packages/frontend/cursor-web + dockerfile: Dockerfile + ports: + - "5173:5173" + environment: + - VITE_BACKEND_URL=http://localhost:3001 + - VITE_WS_URL=ws://localhost:8080 + depends_on: + - backend + networks: + - cursor-network + + code-server: + image: codercom/code-server:latest + ports: + - "8081:8080" + environment: + - PASSWORD=cursor123 + volumes: + - ./workspace:/home/coder/workspace + command: --bind-addr 0.0.0.0:8080 --auth password --disable-telemetry + networks: + - cursor-network + +networks: + cursor-network: + driver: bridge + +volumes: + workspace: \ No newline at end of file diff --git a/cursor-fullstack/packages/backend/claudable/Dockerfile b/cursor-fullstack/packages/backend/claudable/Dockerfile new file mode 100644 index 000000000..91cbccb70 --- /dev/null +++ b/cursor-fullstack/packages/backend/claudable/Dockerfile @@ -0,0 +1,30 @@ +FROM oven/bun:1 as base +WORKDIR /app + +# Install dependencies +COPY package.json bun.lockb* ./ +RUN bun install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the application +RUN bun run build + +# Production stage +FROM oven/bun:1-slim +WORKDIR /app + +# Copy built application +COPY --from=base /app/dist ./dist +COPY --from=base /app/node_modules ./node_modules +COPY --from=base /app/package.json ./ + +# Create workspace directory +RUN mkdir -p /app/workspace + +# Expose ports +EXPOSE 3001 8080 + +# Start the application +CMD ["bun", "run", "start"] \ No newline at end of file diff --git a/cursor-fullstack/packages/backend/claudable/package.json b/cursor-fullstack/packages/backend/claudable/package.json new file mode 100644 index 000000000..56bb9a961 --- /dev/null +++ b/cursor-fullstack/packages/backend/claudable/package.json @@ -0,0 +1,27 @@ +{ + "name": "cursor-backend", + "version": "1.0.0", + "description": "Cursor Full Stack AI IDE Backend", + "main": "src/index.ts", + "scripts": { + "dev": "bun run --hot src/index.ts", + "build": "bun build src/index.ts --outdir dist --target bun", + "start": "bun run dist/index.js" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.24.3", + "@google/generative-ai": "^0.2.1", + "bun-types": "latest", + "cors": "^2.8.5", + "express": "^4.18.2", + "openai": "^4.20.1", + "socket.io": "^4.7.4", + "ws": "^8.14.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/ws": "^8.5.10" + } +} \ No newline at end of file diff --git a/cursor-fullstack/packages/backend/claudable/src/ai/providers.ts b/cursor-fullstack/packages/backend/claudable/src/ai/providers.ts new file mode 100644 index 000000000..e843a4231 --- /dev/null +++ b/cursor-fullstack/packages/backend/claudable/src/ai/providers.ts @@ -0,0 +1,72 @@ +import OpenAI from 'openai'; +import Anthropic from '@anthropic-ai/sdk'; +import { GoogleGenerativeAI } from '@google/generative-ai'; + +interface AIProvider { + chat: (message: string, apiKey: string, model?: string) => Promise; +} + +const openaiProvider: AIProvider = { + async chat(message: string, apiKey: string, model = 'gpt-4') { + const openai = new OpenAI({ apiKey }); + + const completion = await openai.chat.completions.create({ + messages: [{ role: 'user', content: message }], + model: model, + stream: false + }); + + return completion.choices[0]?.message?.content || 'No response generated'; + } +}; + +const anthropicProvider: AIProvider = { + async chat(message: string, apiKey: string, model = 'claude-3-sonnet-20240229') { + const anthropic = new Anthropic({ apiKey }); + + const response = await anthropic.messages.create({ + model: model, + max_tokens: 1000, + messages: [{ role: 'user', content: message }] + }); + + return response.content[0]?.type === 'text' ? response.content[0].text : 'No response generated'; + } +}; + +const googleProvider: AIProvider = { + async chat(message: string, apiKey: string, model = 'gemini-pro') { + const genAI = new GoogleGenerativeAI(apiKey); + const model_instance = genAI.getGenerativeModel({ model: model }); + + const result = await model_instance.generateContent(message); + const response = await result.response; + + return response.text() || 'No response generated'; + } +}; + +const mistralProvider: AIProvider = { + async chat(message: string, apiKey: string, model = 'mistral-large-latest') { + // Using OpenAI-compatible API for Mistral + const openai = new OpenAI({ + apiKey: apiKey, + baseURL: 'https://api.mistral.ai/v1' + }); + + const completion = await openai.chat.completions.create({ + messages: [{ role: 'user', content: message }], + model: model, + stream: false + }); + + return completion.choices[0]?.message?.content || 'No response generated'; + } +}; + +export const aiProviders: Record = { + openai: openaiProvider, + anthropic: anthropicProvider, + google: googleProvider, + mistral: mistralProvider +}; \ No newline at end of file diff --git a/cursor-fullstack/packages/backend/claudable/src/index.ts b/cursor-fullstack/packages/backend/claudable/src/index.ts new file mode 100644 index 000000000..160fd0eca --- /dev/null +++ b/cursor-fullstack/packages/backend/claudable/src/index.ts @@ -0,0 +1,68 @@ +import express from 'express'; +import cors from 'cors'; +import { createServer } from 'http'; +import { Server as SocketIOServer } from 'socket.io'; +import { WebSocketServer } from 'ws'; +import { sessionRoutes } from './routes/session'; +import { setupWebSocket } from './ws'; +import { aiProviders } from './ai/providers'; + +const app = express(); +const server = createServer(app); +const io = new SocketIOServer(server, { + cors: { + origin: "*", + methods: ["GET", "POST"] + } +}); + +// WebSocket server for real-time communication +const wss = new WebSocketServer({ port: 8080 }); + +// Middleware +app.use(cors()); +app.use(express.json()); + +// Routes +app.use('/api', sessionRoutes); + +// AI Providers endpoint +app.get('/api/providers', (req, res) => { + res.json({ + providers: [ + { id: 'openai', name: 'OpenAI', models: ['gpt-4', 'gpt-3.5-turbo'] }, + { id: 'anthropic', name: 'Anthropic', models: ['claude-3-sonnet', 'claude-3-haiku'] }, + { id: 'google', name: 'Google Gemini', models: ['gemini-pro', 'gemini-pro-vision'] }, + { id: 'mistral', name: 'Mistral', models: ['mistral-large', 'mistral-medium'] } + ] + }); +}); + +// Chat endpoint +app.post('/api/chat', async (req, res) => { + try { + const { message, provider, apiKey, model } = req.body; + + if (!message || !provider || !apiKey) { + return res.status(400).json({ error: 'Missing required fields' }); + } + + const response = await aiProviders[provider].chat(message, apiKey, model); + res.json({ response }); + } catch (error) { + console.error('Chat error:', error); + res.status(500).json({ error: 'Failed to process chat request' }); + } +}); + +// Setup WebSocket handlers +setupWebSocket(io, wss); + +const PORT = process.env.PORT || 3001; + +server.listen(PORT, () => { + console.log(`🚀 Backend server running on port ${PORT}`); + console.log(`🔌 WebSocket server running on port 8080`); +}); + +export { app, server, io, wss }; \ No newline at end of file diff --git a/cursor-fullstack/packages/backend/claudable/src/routes/session.ts b/cursor-fullstack/packages/backend/claudable/src/routes/session.ts new file mode 100644 index 000000000..90011909b --- /dev/null +++ b/cursor-fullstack/packages/backend/claudable/src/routes/session.ts @@ -0,0 +1,122 @@ +import { Router } from 'express'; +import { spawn } from 'child_process'; +import path from 'path'; + +const router = Router(); + +// Start code-server session +router.post('/session/start', async (req, res) => { + try { + const { workspace } = req.body; + const workspacePath = workspace || '/app/workspace'; + + // Start code-server process + const codeServer = spawn('code-server', [ + '--bind-addr', '0.0.0.0:8080', + '--auth', 'none', + '--disable-telemetry', + workspacePath + ], { + stdio: 'pipe', + detached: true + }); + + codeServer.stdout?.on('data', (data) => { + console.log(`code-server: ${data}`); + }); + + codeServer.stderr?.on('data', (data) => { + console.error(`code-server error: ${data}`); + }); + + codeServer.on('close', (code) => { + console.log(`code-server process exited with code ${code}`); + }); + + res.json({ + success: true, + message: 'Code server started', + url: 'http://localhost:8081' + }); + } catch (error) { + console.error('Failed to start code-server:', error); + res.status(500).json({ error: 'Failed to start code-server' }); + } +}); + +// Get workspace files +router.get('/workspace/files', async (req, res) => { + try { + const fs = await import('fs/promises'); + const workspacePath = '/app/workspace'; + + const files = await getWorkspaceFiles(workspacePath); + res.json({ files }); + } catch (error) { + console.error('Failed to get workspace files:', error); + res.status(500).json({ error: 'Failed to get workspace files' }); + } +}); + +// Get file content +router.get('/workspace/file/:filepath(*)', async (req, res) => { + try { + const fs = await import('fs/promises'); + const filePath = path.join('/app/workspace', req.params.filepath); + + const content = await fs.readFile(filePath, 'utf-8'); + res.json({ content }); + } catch (error) { + console.error('Failed to read file:', error); + res.status(500).json({ error: 'Failed to read file' }); + } +}); + +// Save file content +router.post('/workspace/file/:filepath(*)', async (req, res) => { + try { + const fs = await import('fs/promises'); + const filePath = path.join('/app/workspace', req.params.filepath); + const { content } = req.body; + + await fs.writeFile(filePath, content, 'utf-8'); + res.json({ success: true }); + } catch (error) { + console.error('Failed to save file:', error); + res.status(500).json({ error: 'Failed to save file' }); + } +}); + +async function getWorkspaceFiles(dir: string, basePath = ''): Promise { + const fs = await import('fs/promises'); + const files = []; + + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + const relativePath = path.join(basePath, entry.name); + + if (entry.isDirectory()) { + if (!entry.name.startsWith('.') && entry.name !== 'node_modules') { + const subFiles = await getWorkspaceFiles(fullPath, relativePath); + files.push(...subFiles); + } + } else { + files.push({ + name: entry.name, + path: relativePath, + type: 'file', + extension: path.extname(entry.name) + }); + } + } + } catch (error) { + console.error('Error reading directory:', error); + } + + return files; +} + +export { router as sessionRoutes }; \ No newline at end of file diff --git a/cursor-fullstack/packages/backend/claudable/src/ws.ts b/cursor-fullstack/packages/backend/claudable/src/ws.ts new file mode 100644 index 000000000..df3363514 --- /dev/null +++ b/cursor-fullstack/packages/backend/claudable/src/ws.ts @@ -0,0 +1,95 @@ +import { Server as SocketIOServer } from 'socket.io'; +import { WebSocketServer } from 'ws'; +import { aiProviders } from './ai/providers'; + +export function setupWebSocket(io: SocketIOServer, wss: WebSocketServer) { + // Socket.IO for real-time communication + io.on('connection', (socket) => { + console.log('Client connected via Socket.IO:', socket.id); + + socket.on('chat-message', async (data) => { + try { + const { message, provider, apiKey, model } = data; + + if (!message || !provider || !apiKey) { + socket.emit('chat-error', { error: 'Missing required fields' }); + return; + } + + // Send typing indicator + socket.emit('typing-start'); + + // Get AI response + const response = await aiProviders[provider].chat(message, apiKey, model); + + // Send response + socket.emit('chat-response', { + response, + provider, + model + }); + + // Stop typing indicator + socket.emit('typing-stop'); + } catch (error) { + console.error('WebSocket chat error:', error); + socket.emit('chat-error', { error: 'Failed to process chat request' }); + socket.emit('typing-stop'); + } + }); + + socket.on('disconnect', () => { + console.log('Client disconnected:', socket.id); + }); + }); + + // Native WebSocket for additional real-time features + wss.on('connection', (ws) => { + console.log('Client connected via WebSocket'); + + ws.on('message', async (data) => { + try { + const message = JSON.parse(data.toString()); + + if (message.type === 'chat') { + const { content, provider, apiKey, model } = message; + + if (!content || !provider || !apiKey) { + ws.send(JSON.stringify({ + type: 'error', + error: 'Missing required fields' + })); + return; + } + + // Send typing indicator + ws.send(JSON.stringify({ type: 'typing-start' })); + + // Get AI response + const response = await aiProviders[provider].chat(content, apiKey, model); + + // Send response + ws.send(JSON.stringify({ + type: 'chat-response', + response, + provider, + model + })); + + // Stop typing indicator + ws.send(JSON.stringify({ type: 'typing-stop' })); + } + } catch (error) { + console.error('WebSocket message error:', error); + ws.send(JSON.stringify({ + type: 'error', + error: 'Failed to process message' + })); + } + }); + + ws.on('close', () => { + console.log('WebSocket client disconnected'); + }); + }); +} \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/Dockerfile b/cursor-fullstack/packages/frontend/cursor-web/Dockerfile new file mode 100644 index 000000000..4b21f927f --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/Dockerfile @@ -0,0 +1,28 @@ +FROM oven/bun:1 as base +WORKDIR /app + +# Install dependencies +COPY package.json bun.lockb* ./ +RUN bun install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the application +RUN bun run build + +# Production stage +FROM nginx:alpine +WORKDIR /usr/share/nginx/html + +# Copy built application +COPY --from=base /app/dist . + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 5173 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/index.html b/cursor-fullstack/packages/frontend/cursor-web/index.html new file mode 100644 index 000000000..db99ddd2c --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/index.html @@ -0,0 +1,13 @@ + + + + + + + Cursor Full Stack AI IDE + + +
+ + + \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/nginx.conf b/cursor-fullstack/packages/frontend/cursor-web/nginx.conf new file mode 100644 index 000000000..1fccd1d7c --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/nginx.conf @@ -0,0 +1,33 @@ +server { + listen 5173; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://backend:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } + + location /socket.io/ { + proxy_pass http://backend:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/package.json b/cursor-fullstack/packages/frontend/cursor-web/package.json new file mode 100644 index 000000000..c34923af6 --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/package.json @@ -0,0 +1,28 @@ +{ + "name": "cursor-frontend", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "bun run vite", + "build": "bun run vite build", + "preview": "bun run vite preview" + }, + "dependencies": { + "@monaco-editor/react": "^4.6.0", + "lucide-react": "^0.294.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "socket.io-client": "^4.7.4", + "tailwindcss": "^3.3.6" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/postcss.config.js b/cursor-fullstack/packages/frontend/cursor-web/postcss.config.js new file mode 100644 index 000000000..e99ebc2c0 --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/src/App.tsx b/cursor-fullstack/packages/frontend/cursor-web/src/App.tsx new file mode 100644 index 000000000..c836de624 --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/src/App.tsx @@ -0,0 +1,95 @@ +import React, { useState, useEffect } from 'react'; +import { Sidebar } from './components/Sidebar'; +import { EditorPanel } from './components/EditorPanel'; +import { ChatAssistant } from './components/ChatAssistant'; +import { ProviderForm } from './components/ProviderForm'; +import { io } from 'socket.io-client'; + +const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3001'; +const WS_URL = import.meta.env.VITE_WS_URL || 'ws://localhost:8080'; + +function App() { + const [selectedFile, setSelectedFile] = useState(null); + const [files, setFiles] = useState([]); + const [showChat, setShowChat] = useState(false); + const [showProviderForm, setShowProviderForm] = useState(false); + const [socket, setSocket] = useState(null); + const [apiKeys, setApiKeys] = useState>({}); + const [selectedProvider, setSelectedProvider] = useState('openai'); + + useEffect(() => { + // Initialize Socket.IO connection + const newSocket = io(BACKEND_URL); + setSocket(newSocket); + + // Load workspace files + loadWorkspaceFiles(); + + return () => { + newSocket.close(); + }; + }, []); + + const loadWorkspaceFiles = async () => { + try { + const response = await fetch(`${BACKEND_URL}/api/workspace/files`); + const data = await response.json(); + setFiles(data.files || []); + } catch (error) { + console.error('Failed to load workspace files:', error); + } + }; + + const handleFileSelect = (filePath: string) => { + setSelectedFile(filePath); + }; + + const handleApiKeySave = (provider: string, apiKey: string) => { + setApiKeys(prev => ({ ...prev, [provider]: apiKey })); + setShowProviderForm(false); + }; + + return ( +
+ {/* Sidebar */} + setShowChat(!showChat)} + onShowProviderForm={() => setShowProviderForm(true)} + showChat={showChat} + /> + + {/* Main Content */} +
+ {/* Editor Panel */} + + + {/* Chat Assistant */} + {showChat && ( + + )} +
+ + {/* Provider Form Modal */} + {showProviderForm && ( + setShowProviderForm(false)} + existingKeys={apiKeys} + /> + )} +
+ ); +} + +export default App; \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/src/components/Sidebar.tsx b/cursor-fullstack/packages/frontend/cursor-web/src/components/Sidebar.tsx new file mode 100644 index 000000000..a1c091b1a --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/src/components/Sidebar.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { + MessageSquare, + Settings, + File, + Folder, + FolderOpen, + ChevronRight, + ChevronDown +} from 'lucide-react'; + +interface SidebarProps { + files: any[]; + selectedFile: string | null; + onFileSelect: (filePath: string) => void; + onShowChat: () => void; + onShowProviderForm: () => void; + showChat: boolean; +} + +export const Sidebar: React.FC = ({ + files, + selectedFile, + onFileSelect, + onShowChat, + onShowProviderForm, + showChat +}) => { + const [expandedFolders, setExpandedFolders] = useState>(new Set()); + + const toggleFolder = (folderPath: string) => { + const newExpanded = new Set(expandedFolders); + if (newExpanded.has(folderPath)) { + newExpanded.delete(folderPath); + } else { + newExpanded.add(folderPath); + } + setExpandedFolders(newExpanded); + }; + + const renderFileTree = (files: any[], level = 0) => { + const groupedFiles = files.reduce((acc, file) => { + const pathParts = file.path.split('/'); + const folder = pathParts.slice(0, -1).join('/'); + + if (!acc[folder]) { + acc[folder] = []; + } + acc[folder].push(file); + return acc; + }, {} as Record); + + return Object.entries(groupedFiles).map(([folder, folderFiles]) => { + const isExpanded = expandedFolders.has(folder); + const hasSubfolders = folderFiles.some(f => f.type === 'directory'); + + return ( +
+ {folder && ( +
toggleFolder(folder)} + > + {hasSubfolders && ( +
+ {isExpanded ? ( + + ) : ( + + )} +
+ )} + {!hasSubfolders &&
} + + {folder.split('/').pop() || 'Root'} +
+ )} + + {isExpanded && ( +
+ {folderFiles.map((file) => ( +
onFileSelect(file.path)} + > + + {file.name} +
+ ))} +
+ )} +
+ ); + }); + }; + + return ( +
+ {/* Header */} +
+

Cursor IDE

+
+ + {/* Navigation */} +
+ +
+ + {/* File Explorer */} +
+
+
EXPLORER
+ {renderFileTree(files)} +
+
+ + {/* Settings */} +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/src/index.css b/cursor-fullstack/packages/frontend/cursor-web/src/index.css new file mode 100644 index 000000000..27751c19d --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/src/index.css @@ -0,0 +1,48 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +* { + 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-color: #1e1e1e; + color: #cccccc; + overflow: hidden; +} + +#root { + height: 100vh; + width: 100vw; +} + +.monaco-editor { + height: 100% !important; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #1e1e1e; +} + +::-webkit-scrollbar-thumb { + background: #3c3c3c; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/src/main.tsx b/cursor-fullstack/packages/frontend/cursor-web/src/main.tsx new file mode 100644 index 000000000..cbe1cdf3c --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/tailwind.config.js b/cursor-fullstack/packages/frontend/cursor-web/tailwind.config.js new file mode 100644 index 000000000..ef88bfda3 --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/tailwind.config.js @@ -0,0 +1,20 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + 'cursor-bg': '#1e1e1e', + 'cursor-sidebar': '#252526', + 'cursor-border': '#3c3c3c', + 'cursor-text': '#cccccc', + 'cursor-accent': '#007acc', + 'cursor-hover': '#2a2d2e', + } + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/cursor-fullstack/packages/frontend/cursor-web/vite.config.ts b/cursor-fullstack/packages/frontend/cursor-web/vite.config.ts new file mode 100644 index 000000000..d77a7a20a --- /dev/null +++ b/cursor-fullstack/packages/frontend/cursor-web/vite.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + host: '0.0.0.0', + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true + }, + '/socket.io': { + target: 'http://localhost:3001', + changeOrigin: true, + ws: true + } + } + }, + build: { + outDir: 'dist', + sourcemap: false + } +}) \ No newline at end of file