From 6400a5eacb7f43930cc82349450b05a216d3ef6e Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 30 Nov 2025 11:59:46 -0500 Subject: [PATCH] Add automatic code error fixing feature Implements an AI-powered auto-fix system that automatically attempts to repair code execution errors up to 3 times. Backend changes (app.py): - Add /api/fix-code endpoint that uses MODEL_1 (Hermes) to analyze stderr output and generate corrected code - System prompt instructs AI to output only raw fixed code without explanations or markdown formatting - Accepts code, language, stderr, exit_code, and attempt number Frontend changes (templates/chat.html): - Modify executeCodeBlock() to detect failed executions (exit_code !== 0) - Track fix attempts per code block (max 3) using dataset attributes - Call /api/fix-code when errors are detected - Display fixed code as new chat message with markdown formatting - Automatically re-execute the corrected code recursively - Show progress messages during auto-fix attempts - Display warning when max attempts (3) are exhausted Features: - Fixes common issues: missing imports, syntax errors, type errors - Posts fixed code to chat for transparency - Prevents infinite loops with 3-attempt limit - Graceful error handling with user-friendly status messages --- app.py | 82 ++++++++++++++++++++++++++++++++++++++++++ templates/chat.html | 87 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/app.py b/app.py index 96d2a1e..d9be3c6 100644 --- a/app.py +++ b/app.py @@ -850,6 +850,88 @@ Examples: return jsonify({"filename": "compiled_binary"}) +@app.route("/api/fix-code", methods=["POST"]) +def fix_code(): + """Auto-fix code errors by asking AI to fix issues based on stderr output. + + Accepts code, language, stderr, and attempt number. + Returns fixed code block or error message. + """ + try: + data = request.get_json() + code = data.get("code", "") + language = data.get("language", "") + stderr = data.get("stderr", "") + exit_code = data.get("exit_code", 1) + attempt = data.get("attempt", 1) + + if not code or not language: + return jsonify({"error": "Code and language are required"}), 400 + + # Use MODEL_1 (Hermes) to fix the code + client, model = get_openai_client_and_model("MODEL_1") + + system_prompt = f"""You are an expert {language} programmer and debugger. Your task is to fix code that has errors. + +CRITICAL RULES: +- Output ONLY the fixed code, nothing else +- NO explanations, NO comments about what you changed +- NO markdown code fences (```), just the raw code +- Preserve the original code structure and logic as much as possible +- Fix ONLY the errors reported in stderr +- If the error mentions missing imports/includes, add them at the top +- If the error is a syntax error, fix the syntax +- Keep the same variable names and overall approach + +The code should be immediately executable without any modifications.""" + + user_prompt = f"""The following {language} code has errors: + +```{language} +{code} +``` + +Error output (exit code {exit_code}): +``` +{stderr} +``` + +Fix the code (output ONLY the corrected code, no explanations):""" + + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + temperature=0.2, # Low temperature for consistent fixes + max_tokens=2000 + ) + + fixed_code = response.choices[0].message.content.strip() + + # Clean up any markdown code fences that might have slipped through + if fixed_code.startswith("```"): + lines = fixed_code.split("\n") + # Remove first line if it's a fence + if lines[0].startswith("```"): + lines = lines[1:] + # Remove last line if it's a fence + if lines and lines[-1].strip() == "```": + lines = lines[:-1] + fixed_code = "\n".join(lines) + + return jsonify({ + "success": True, + "fixed_code": fixed_code, + "attempt": attempt + }) + + except Exception as e: + print(f"Error fixing code: {e}") + return jsonify({"error": f"Failed to fix code: {str(e)}"}), 500 + + @app.route("/chat/") def chat(room_name): user = auth.get_current_user() diff --git a/templates/chat.html b/templates/chat.html index c83e6d5..903c35d 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -1552,6 +1552,93 @@ async function executeCodeBlock(code, blockElement, playButton) { if (job.status === 'completed') { // Unsandbox returns stdout/stderr/exit_code at top level of job response displayExecutionResults(job, resultsContainer, language, code); + + // Check if execution failed and attempt auto-fix + const exitCode = job.exit_code; + const stderr = job.stderr || ''; + const shouldAutoFix = exitCode !== 0 && stderr.trim() !== ''; + + // Track attempts per code block (store in resultsContainer dataset) + if (!resultsContainer.dataset.fixAttempts) { + resultsContainer.dataset.fixAttempts = '0'; + } + + const currentAttempts = parseInt(resultsContainer.dataset.fixAttempts); + + if (shouldAutoFix && currentAttempts < 3) { + // Show auto-fix message + const autoFixDiv = document.createElement('div'); + autoFixDiv.style.color = 'var(--text-info)'; + autoFixDiv.style.fontWeight = 'bold'; + autoFixDiv.style.marginTop = '12px'; + autoFixDiv.textContent = `Attempting to auto-fix errors (attempt ${currentAttempts + 1}/3)...`; + resultsContainer.appendChild(autoFixDiv); + + // Increment attempt counter + resultsContainer.dataset.fixAttempts = (currentAttempts + 1).toString(); + + // Call backend to fix the code + try { + const fixResponse = await fetch('/api/fix-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + code: code, + language: language, + stderr: stderr, + exit_code: exitCode, + attempt: currentAttempts + 1 + }) + }); + + if (fixResponse.ok) { + const fixData = await fixResponse.json(); + + if (fixData.success && fixData.fixed_code) { + // Update status + autoFixDiv.textContent = `Code fixed! Re-executing (attempt ${currentAttempts + 1}/3)...`; + + // Post the fixed code as a new message in the chat + socket.emit("chat_message", { + "username": username, + "message": `**Auto-fixed code (attempt ${currentAttempts + 1}/3):**\n\n\`\`\`${language}\n${fixData.fixed_code}\n\`\`\``, + "model": "None", + "room_name": room_name + }); + + // Wait a moment for the message to be posted + await sleep(500); + + // Re-execute with the fixed code + // Create a small delay to prevent stack overflow + await sleep(200); + await executeCodeBlock(fixData.fixed_code, blockElement, playButton); + return; // Exit this execution, the recursive call will handle the rest + } else { + autoFixDiv.textContent = `Auto-fix failed: ${fixData.error || 'Unknown error'}`; + autoFixDiv.style.color = 'var(--text-error)'; + } + } else { + autoFixDiv.textContent = `Auto-fix request failed (HTTP ${fixResponse.status})`; + autoFixDiv.style.color = 'var(--text-error)'; + } + } catch (autoFixError) { + console.error('Error during auto-fix:', autoFixError); + autoFixDiv.textContent = `Auto-fix error: ${autoFixError.message}`; + autoFixDiv.style.color = 'var(--text-error)'; + } + } else if (currentAttempts >= 3 && shouldAutoFix) { + // Max attempts reached + const maxAttemptsDiv = document.createElement('div'); + maxAttemptsDiv.style.color = 'var(--text-warning)'; + maxAttemptsDiv.style.fontWeight = 'bold'; + maxAttemptsDiv.style.marginTop = '12px'; + maxAttemptsDiv.textContent = 'Maximum auto-fix attempts (3) reached. Code still has errors.'; + resultsContainer.appendChild(maxAttemptsDiv); + } + break; }