faaiza/CodeSage
0
1require('dotenv').config();2const express = require('express');3const cors = require('cors');4const fetch = require('node-fetch');5const path = require('path');6 7const app = express();8const PORT = process.env.PORT || 3000;9 10app.use(cors());11app.use(express.json());12app.use(express.static(path.join(__dirname, 'public')));13 14// Health check route15app.get('/health', (req, res) => {16 res.json({ status: 'ok', message: 'CodeSage server is running' });17});18 19// Main AI route โ streams response from Groq back to browser20app.post('/api/analyze', async (req, res) => {21 const { prompt, code, action } = req.body;22 23 if (!action) {24 return res.status(400).json({ error: 'No action provided' });25 }26 27 // Validate input (custom questions may omit code)28 if (action !== 'custom' && (!code || code.trim() === '')) {29 return res.status(400).json({ error: 'No code provided' });30 }31 32 // Build the system prompt based on action33 const systemPrompts = {34 explain: 'You are a code explainer. Explain this code in simple english. Format: 1) Purpose (1 sentence) 2) How it works (max 4 bullet points, each one line) 3) Issues (max 2 bullets). Be concise.',35 36 refactor: 'You are a code refactorer. Fix ALL issues: SQL injection must use parameterized queries, add error handling, remove sensitive data exposure, use modern syntax. Show the complete improved code in one code block. Then list changes in max 4 bullet points.',37 38 bugs: 'You are a code debugger. Respond in EXACTLY this format, no exceptions:\n\n๐ด CRITICAL (security/crashes):\n- issue: fix\n\n๐ก WARNING (runtime errors):\n- issue: fix\n\n๐ต SUGGESTION (improvements):\n- issue: fix\n\nMax 3 items per category. Skip empty categories. Never call style issues bugs.',39 40 tests: 'You are a test writer. Write practical tests using pytest for Python, Jest for JavaScript, PHPUnit for PHP. Cover: happy path, one error case, one edge case. Keep each test short. Show complete runnable test file.',41 42 improve: 'You are a code reviewer. Give exactly 4 improvements. Format each as: **Title** - one line description, then show only the specific improved code snippet. No long paragraphs.',43 44 animate: 'You are a code execution visualizer. Return ONLY valid JSON. Zero text outside JSON. Zero markdown. Zero backticks. Structure: {"steps":[{"lineNumbers":[1],"explanation":"plain english, max 20 words","variables":[{"name":"x","value":"10","type":"number","isNew":true}],"callStack":[{"name":"main","isActive":true}],"status":"normal"}]}. CRITICAL: The user message states the exact line count N โ you MUST return exactly N steps in order. Step 1 covers line 1 only (lineNumbers:[1]), step 2 covers line 2 only (lineNumbers:[2]), etc. Never skip, merge, or group lines. Every line gets its own step from first to last. For complex values use short strings like "[n items]" or "{n keys}". Status: normal, highlight, error, or return. Always valid JSON.'45 };46 47 const tokenLimits = {48 explain: 500,49 refactor: 900,50 bugs: 600,51 tests: 900,52 improve: 700,53 animate: 120054 };55 56 const systemMessage = systemPrompts[action] || systemPrompts.explain;57 58 let userMessage;59 if (action === 'custom' && prompt) {60 userMessage = prompt;61 } else if (action === 'animate') {62 const codeLines = code.split('\n');63 const numbered = codeLines.map((line, i) => `${i + 1}: ${line}`).join('\n');64 userMessage = `This code has exactly ${codeLines.length} lines. Generate exactly ${codeLines.length} animation steps โ one step per line, sequential, no skipping.\n\n${numbered}`;65 } else {66 userMessage = `Here is the code to analyze:\n\n\`\`\`\n${code}\n\`\`\``;67 }68 69 const animateTokenLimit = action === 'animate'70 ? Math.min(4000, Math.max(1200, code.split('\n').length * 140))71 : (tokenLimits[action] || 800);72 73 // Set headers for Server-Sent Events streaming74 res.setHeader('Content-Type', 'text/event-stream');75 res.setHeader('Cache-Control', 'no-cache');76 res.setHeader('Connection', 'keep-alive');77 res.setHeader('Access-Control-Allow-Origin', '*');78 res.flushHeaders();79 80 try {81 const groqResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', {82 method: 'POST',83 headers: {84 'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,85 'Content-Type': 'application/json'86 },87 body: JSON.stringify({88 model: 'llama-3.3-70b-versatile',89 messages: [90 { role: 'system', content: systemMessage },91 { role: 'user', content: userMessage }92 ],93 max_tokens: animateTokenLimit,94 temperature: 0.2,95 stream: true96 })97 });98 99 if (!groqResponse.ok) {100 const errorBody = await groqResponse.text();101 console.error('Groq API error:', groqResponse.status, errorBody);102 res.write(`data: ${JSON.stringify({ error: 'Groq API error: ' + groqResponse.status })}\n\n`);103 res.end();104 return;105 }106 107 // Stream Groq response to the browser108 const reader = groqResponse.body;109 let buffer = '';110 111 reader.on('data', (chunk) => {112 buffer += chunk.toString();113 const lines = buffer.split('\n');114 buffer = lines.pop(); // keep incomplete line in buffer115 116 for (const line of lines) {117 const trimmed = line.trim();118 if (!trimmed || !trimmed.startsWith('data: ')) continue;119 120 const data = trimmed.slice(6);121 if (data === '[DONE]') {122 res.write('data: [DONE]\n\n');123 continue;124 }125 126 try {127 const parsed = JSON.parse(data);128 const content = parsed.choices?.[0]?.delta?.content;129 if (content) {130 res.write(`data: ${JSON.stringify({ text: content })}\n\n`);131 }132 } catch (e) {133 // Skip malformed chunks134 }135 }136 });137 138 reader.on('end', () => {139 res.write('data: [DONE]\n\n');140 res.end();141 });142 143 reader.on('error', (err) => {144 console.error('Stream error:', err);145 res.write(`data: ${JSON.stringify({ error: 'Stream error' })}\n\n`);146 res.end();147 });148 149 } catch (err) {150 console.error('Server error:', err);151 res.write(`data: ${JSON.stringify({ error: 'Server error: ' + err.message })}\n\n`);152 res.end();153 }154});155 156// Catch-all: serve index.html for any unknown route157app.get('*', (req, res) => {158 res.sendFile(path.join(__dirname, 'public', 'index.html'));159});160 161app.listen(PORT, () => {162 console.log(`โ
CodeSage server running at http://localhost:${PORT}`);163 console.log(` Groq API key loaded: ${process.env.GROQ_API_KEY ? 'YES โ' : 'NO โ โ check your .env file'}`);164});165 