commit ff407690ce63ff2422b3f914d45499bc5ef32057
parent 8d3e3d8e24fa6d52ce1716d7173e523c16bf77cd
Author: Amit Dutta <amitdutta4255@gmail.com>
Date: Tue, 13 Jan 2026 19:10:46 +0530
[2026-01-13] : added a new feature (AI debugging, Powered by Gemini 2.5 Flash)
Diffstat:
| M | backend.py | | | 79 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
| M | public/c.html | | | 277 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
| M | public/cpp.html | | | 215 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
| M | public/docs.html | | | 58 | ++++++++++++++++++++++++++++++++++++---------------------- |
| M | public/index.html | | | 11 | ++++++++++- |
| M | public/python.html | | | 210 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
6 files changed, 729 insertions(+), 121 deletions(-)
diff --git a/backend.py b/backend.py
@@ -1,4 +1,5 @@
import asyncio
+import aiohttp # Added missing import
from aiohttp import web
import json
import subprocess
@@ -7,12 +8,15 @@ import sys
import threading
import re
import importlib.util
-import platform # <--- FIXED: Added missing import
+import platform
# ==================================================================================
# CLOUD COMPILER SERVER (AIOHTTP) - RENDER COMPATIBLE
# ==================================================================================
+# Get API Key from Environment
+GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
+
def install_package(package_name, ws=None, loop=None):
"""Attempt to install a python package via pip."""
try:
@@ -43,13 +47,12 @@ def check_and_install_packages(code, ws=None, loop=None):
from_imports = re.findall(r'^\s*from\s+(\w+)', code, re.MULTILINE)
unique_packages = set(imports + from_imports)
for pkg in unique_packages:
- if pkg in ['os', 'sys', 'time', 'random', 'math', 'json', 'asyncio', 'threading', 'platform', 'subprocess', 're']:
+ if pkg in ['os', 'sys', 'time', 'random', 'math', 'json', 'asyncio', 'threading', 'platform', 'subprocess', 're', 'aiohttp']:
continue
install_package(pkg, ws, loop)
async def handle_client(request):
# --- HEALTH CHECK HANDLING ---
- # Render sends HEAD/GET to root. If not a websocket upgrade, return OK.
if request.headers.get("Upgrade", "").lower() != "websocket":
return web.Response(text="OK")
@@ -64,23 +67,18 @@ async def handle_client(request):
def read_stream(stream, loop):
try:
while True:
- # Read 1 byte/char at a time
char = stream.read(1)
if not char:
break
- # aiohttp's send_json is a coroutine
asyncio.run_coroutine_threadsafe(
ws.send_json({'type': 'stdout', 'data': char}),
loop
)
- # --- SIGNAL FINISH ---
- # When stream ends (process exits), tell frontend
asyncio.run_coroutine_threadsafe(
ws.send_json({'type': 'status', 'msg': 'Program finished'}),
loop
)
-
except Exception:
pass
@@ -136,6 +134,8 @@ async def handle_client(request):
if compile_process.returncode != 0:
await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
+ # Send explicit error status so frontend knows to show AI button
+ await ws.send_json({'type': 'status', 'msg': 'Compilation Failed'})
continue
await ws.send_json({'type': 'status', 'msg': 'Running C Binary...'})
@@ -168,6 +168,7 @@ async def handle_client(request):
if compile_process.returncode != 0:
await ws.send_json({'type': 'stdout', 'data': f"Compilation Error:\n{compile_process.stderr}"})
+ await ws.send_json({'type': 'status', 'msg': 'Compilation Failed'})
continue
await ws.send_json({'type': 'status', 'msg': 'Running C++ Binary...'})
@@ -196,12 +197,68 @@ async def handle_client(request):
process.stdin.flush()
except Exception:
pass
-
+
+ # --- NEW: AI FIX HANDLER ---
+ elif data.get('type') == 'ai_fix':
+ code = data.get('code')
+ error_log = data.get('error')
+ language = data.get('language', 'c') # Default to C, but respect frontend input
+
+ if not GEMINI_API_KEY:
+ await ws.send_json({'type': 'ai_error', 'msg': 'Server Error: GEMINI_API_KEY not configured.'})
+ continue
+
+ # Construct Prompt
+ json_format = '{\n "explanation": "Brief explanation of the bug (max 2 sentences)",\n "fixed_code": "The full corrected code"\n}'
+
+ prompt = (
+ f"You are an expert {language.upper()} programming debugger.\n"
+ f"CODE:\n{code}\n"
+ f"ERROR OUTPUT:\n{error_log}\n"
+ "TASK:\n"
+ "1. Analyze the error.\n"
+ "2. Provide a concise explanation.\n"
+ "3. Provide the COMPLETE corrected code.\n"
+ "RESPONSE FORMAT:\n"
+ "Return ONLY a valid JSON object with no markdown formatting or backticks:\n"
+ f"{json_format}"
+ )
+
+ try:
+ api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key={GEMINI_API_KEY}"
+
+ async with aiohttp.ClientSession() as session:
+ async with session.post(api_url, json={
+ "contents": [{ "parts": [{ "text": prompt }] }],
+ "generationConfig": { "responseMimeType": "application/json" }
+ }) as resp:
+ if resp.status != 200:
+ error_text = await resp.text()
+ await ws.send_json({'type': 'ai_error', 'msg': f"AI API Error: {error_text}"})
+ else:
+ result = await resp.json()
+ # Extract text from Gemini response structure
+ content_text = result.get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '{}')
+
+ # Send back to client
+ await ws.send_json({
+ 'type': 'ai_response',
+ 'data': json.loads(content_text)
+ })
+
+ except Exception as e:
+ print(f"AI Error: {e}")
+ await ws.send_json({'type': 'ai_error', 'msg': f"Server Processing Error: {str(e)}"})
+
elif msg.type == web.WSMsgType.ERROR:
print(f'ws connection closed with exception {ws.exception()}')
finally:
- if process: process.kill()
+ if process:
+ try:
+ process.kill()
+ except:
+ pass
print("Client disconnected")
return ws
@@ -209,7 +266,7 @@ async def handle_client(request):
async def main():
port = int(os.environ.get("PORT", 8765))
app = web.Application()
- # Route root path to the unified handler (handles both HTTP checks and WS upgrades)
+ # Route root path to the unified handler
app.add_routes([web.get('/', handle_client)])
runner = web.AppRunner(app)
diff --git a/public/c.html b/public/c.html
@@ -145,6 +145,67 @@
70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
+
+ /* AI Text Overlay */
+ .ai-overlay {
+ position: absolute;
+ bottom: 10px;
+ right: 20px;
+ background: rgba(20, 20, 20, 0.9);
+ border: 1px solid #ef4444;
+ color: #fca5a5;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ z-index: 60;
+ display: none;
+ backdrop-filter: blur(4px);
+ animation: slideIn 0.3s ease-out;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
+ }
+ .ai-overlay.visible { display: flex; align-items: center; gap: 8px; }
+
+ .ai-link {
+ color: #fff;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ text-underline-offset: 4px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: color 0.2s;
+ }
+ .ai-link:hover { color: #bef264; text-decoration-style: solid; }
+
+ @keyframes slideIn {
+ from { transform: translateY(20px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+ }
+
+ /* AI Modal */
+ .modal-overlay {
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
+ background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);
+ z-index: 200; display: none; align-items: center; justify-content: center;
+ }
+ .modal-overlay.open { display: flex; }
+
+ .modal-content {
+ background: #0f0f0f;
+ border: 1px solid var(--accent);
+ width: 90%; max-width: 600px;
+ max-height: 80vh;
+ border-radius: 12px;
+ display: flex; flex-direction: column;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ overflow: hidden;
+ animation: modalPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ @keyframes modalPop {
+ 0% { transform: scale(0.9); opacity: 0; }
+ 100% { transform: scale(1); opacity: 1; }
+ }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -153,6 +214,59 @@
<div class="crt-scanline"></div>
<div id="toast" class="toast">Link Copied to Clipboard!</div>
+ <!-- AI Overlay in Terminal -->
+ <div id="ai-suggestion" class="ai-overlay">
+ <i class="fas fa-exclamation-triangle text-red-500"></i>
+ <span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span>
+ </div>
+
+ <!-- AI Fix Modal -->
+ <div id="ai-modal" class="modal-overlay">
+ <div class="modal-content">
+ <!-- Modal Header -->
+ <div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center">
+ <h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2">
+ <i class="fas fa-robot"></i> AI Diagnosis
+ </h3>
+ <button id="close-modal" class="text-neutral-500 hover:text-white transition-colors">
+ <i class="fas fa-times"></i>
+ </button>
+ </div>
+
+ <!-- Modal Body -->
+ <div class="p-6 overflow-y-auto custom-scrollbar flex-1">
+ <div id="ai-loading" class="hidden flex flex-col items-center justify-center py-8 gap-4">
+ <div class="pulse-dot connected" style="width: 12px; height: 12px;"></div>
+ <p class="font-mono text-neutral-400 text-sm animate-pulse">Analyzing compile error...</p>
+ </div>
+
+ <div id="ai-result" class="hidden space-y-4">
+ <div>
+ <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Explanation</h4>
+ <p id="ai-explanation" class="text-sm text-neutral-200 leading-relaxed font-sans"></p>
+ </div>
+
+ <div>
+ <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Suggested Fix</h4>
+ <div class="relative group">
+ <pre id="ai-code-preview" class="bg-black/30 border border-white/10 p-3 rounded text-xs font-mono text-lime-100 overflow-x-auto custom-scrollbar"></pre>
+ </div>
+ </div>
+ </div>
+
+ <div id="ai-error-msg" class="hidden text-red-400 text-sm font-mono text-center py-4"></div>
+ </div>
+
+ <!-- Modal Footer -->
+ <div class="bg-white/5 px-6 py-4 border-t border-white/10 flex justify-end gap-3">
+ <button id="cancel-fix-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all">Dismiss</button>
+ <button id="apply-fix-btn" disabled class="bg-lime-300/10 hover:bg-lime-300 hover:text-black text-lime-300 border border-lime-300/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
+ <i class="fas fa-check"></i> Apply Fix
+ </button>
+ </div>
+ </div>
+ </div>
+
<!-- Header (Responsive) -->
<header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4">
@@ -225,14 +339,10 @@ int main() {
}</textarea>
</div>
- <!-- AI Fix Button -->
- <button id="ai-fix-btn" class="hidden absolute bottom-4 right-4 bg-red-500/10 border border-red-500/50 text-red-400 hover:bg-red-500 hover:text-white px-3 py-1.5 rounded-lg text-xs font-mono transition-all items-center gap-2 backdrop-blur-md z-20">
- <i class="fas fa-magic"></i> Fix with AI
- </button>
</div>
<!-- Terminal -->
- <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505]">
+ <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505] relative">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>OUT // TERMINAL</span>
<span class="text-neutral-600">XTERM.JS</span>
@@ -264,12 +374,10 @@ int main() {
const db = getFirestore(app);
const auth = getAuth(app);
- // Attempt initial sign-in, but don't crash if it fails (restricted op)
signInAnonymously(auth).catch((error) => {
- console.warn("Auto-Auth failed (likely disabled in console). App will try unauthenticated access.", error.code);
+ console.warn("Auto-Auth failed. App will try unauthenticated access.", error.code);
});
- // --- Helper: Generate ID ---
function generateId(length = 7) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
@@ -284,16 +392,29 @@ int main() {
const resetBtn = document.getElementById('reset-btn');
const shareBtn = document.getElementById('share-btn');
const copyBtn = document.getElementById('copy-btn');
- const aiFixBtn = document.getElementById('ai-fix-btn');
const connDot = document.getElementById('conn-dot');
const connectionStatus = document.getElementById('connection-status');
const codeInput = document.getElementById('code-input');
const toast = document.getElementById('toast');
+
+ // AI Elements
+ const aiSuggestion = document.getElementById('ai-suggestion');
+ const askAiLink = document.getElementById('ask-ai-link');
+ const aiModal = document.getElementById('ai-modal');
+ const closeModal = document.getElementById('close-modal');
+ const cancelFixBtn = document.getElementById('cancel-fix-btn');
+ const applyFixBtn = document.getElementById('apply-fix-btn');
+ const aiLoading = document.getElementById('ai-loading');
+ const aiResult = document.getElementById('ai-result');
+ const aiExplanation = document.getElementById('ai-explanation');
+ const aiCodePreview = document.getElementById('ai-code-preview');
+ const aiErrorMsg = document.getElementById('ai-error-msg');
// --- State ---
let socket = null;
let currentLine = "";
let startTime = 0;
+ let lastSuggestedCode = "";
// --- EDITOR LOGIC ---
codeInput.addEventListener('keydown', function(e) {
@@ -334,7 +455,6 @@ int main() {
const sharedCodeOld = params.get('code');
if (shareId) {
- // Fetch from Firebase
try {
toast.textContent = "Loading shared code...";
toast.classList.add('show');
@@ -345,31 +465,22 @@ int main() {
if (docSnap.exists()) {
const data = docSnap.data();
codeInput.value = data.code;
-
- // Restore Output if it exists
if (data.output && term) {
term.write('\r\n\x1b[38;2;115;115;115m> PREVIOUS OUTPUT:\x1b[0m\r\n');
term.write(data.output.replace(/\n/g, '\r\n'));
term.write('\r\n\x1b[38;2;115;115;115m> ----------------\x1b[0m\r\n');
}
-
toast.textContent = "Code loaded successfully";
} else {
toast.textContent = "Shared code not found";
}
setTimeout(() => toast.classList.remove('show'), 3000);
} catch (e) {
- console.error("Fetch error:", e);
- // More descriptive error for user
- if (e.code === 'permission-denied') {
- toast.textContent = "Error: Database Access Denied";
- } else {
- toast.textContent = "Error loading code";
- }
- setTimeout(() => toast.classList.remove('show'), 3000);
+ console.error("Fetch error:", e);
+ toast.textContent = "Error loading code";
+ setTimeout(() => toast.classList.remove('show'), 3000);
}
} else if (sharedCodeOld) {
- // Fallback for Base64
try { codeInput.value = atob(sharedCodeOld); }
catch(e) {}
}
@@ -426,18 +537,6 @@ int main() {
return text.trim();
}
- // Custom Key Handler for Ctrl+C Copy
- term.attachCustomKeyEventHandler((arg) => {
- if (arg.ctrlKey && arg.code === "KeyC" && arg.type === "keydown") {
- const selection = term.getSelection();
- if (selection) {
- fallbackCopyTextToClipboard(selection);
- return false;
- }
- }
- return true;
- });
-
term.write('\x1b[38;2;115;115;115m> SYSTEM READY.\r\n> ATTEMPTING CONNECTION...\x1b[0m\r\n');
// --- WebSocket Logic ---
@@ -473,8 +572,9 @@ int main() {
if (msg.type === 'stdout') {
let text = msg.data;
+ // Trigger AI Overlay on Error
if (text.toLowerCase().includes("error:") || text.toLowerCase().includes("warning:")) {
- aiFixBtn.classList.remove('hidden');
+ aiSuggestion.classList.add('visible');
}
if (!text.includes('\r')) {
@@ -482,11 +582,16 @@ int main() {
}
term.write(text);
} else if (msg.type === 'status') {
- // Detect "Program finished" signal from backend
if (msg.msg.includes("Program finished")) {
const duration = (Date.now() - startTime) / 1000;
term.write(`\r\n\x1b[38;2;115;115;115m[Finished in ${duration.toFixed(3)}s]\x1b[0m\r\n`);
+ } else if (msg.msg === "Compilation Failed") {
+ aiSuggestion.classList.add('visible');
}
+ } else if (msg.type === 'ai_response') {
+ handleAiResponse(msg.data);
+ } else if (msg.type === 'ai_error') {
+ showAiError(msg.msg);
}
};
}
@@ -498,7 +603,7 @@ int main() {
document.activeElement.blur();
term.reset();
currentLine = "";
- aiFixBtn.classList.add('hidden');
+ aiSuggestion.classList.remove('visible'); // Hide previous suggestions
let code = codeInput.value;
code = code.replace(/\u00A0/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
@@ -526,25 +631,76 @@ int main() {
term.focus();
});
- // --- Reset Logic ---
- resetBtn.addEventListener('click', () => {
- term.reset();
- currentLine = "";
- term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> ./a.out\r\n`);
+ // --- AI Feature Logic ---
+
+ // 1. Open Modal and Request AI Fix
+ askAiLink.addEventListener('click', () => {
+ const errorContext = getTerminalContent();
+ const currentCode = codeInput.value;
+
+ // UI Update
+ aiModal.classList.add('open');
+ aiLoading.classList.remove('hidden');
+ aiResult.classList.add('hidden');
+ aiErrorMsg.classList.add('hidden');
+ applyFixBtn.disabled = true;
+
+ // Send Request
+ socket.send(JSON.stringify({
+ type: 'ai_fix',
+ code: currentCode,
+ error: errorContext,
+ language: 'c'
+ }));
+ });
+
+ // 2. Handle AI Response
+ function handleAiResponse(data) {
+ aiLoading.classList.add('hidden');
+ aiResult.classList.remove('hidden');
+
+ aiExplanation.textContent = data.explanation;
+ aiCodePreview.textContent = data.fixed_code;
+ lastSuggestedCode = data.fixed_code;
+
+ applyFixBtn.disabled = false;
+ }
+
+ // 3. Handle AI Error
+ function showAiError(msg) {
+ aiLoading.classList.add('hidden');
+ aiErrorMsg.textContent = msg;
+ aiErrorMsg.classList.remove('hidden');
+ }
+
+ // 4. Apply Fix
+ applyFixBtn.addEventListener('click', () => {
+ if (lastSuggestedCode) {
+ codeInput.value = lastSuggestedCode;
+ closeAiModal();
+ showToast("Fix Applied Successfully!");
+ aiSuggestion.classList.remove('visible');
+ }
+ });
+
+ // 5. Modal Controls
+ function closeAiModal() {
+ aiModal.classList.remove('open');
+ }
+ closeModal.addEventListener('click', closeAiModal);
+ cancelFixBtn.addEventListener('click', closeAiModal);
+ aiModal.addEventListener('click', (e) => {
+ if (e.target === aiModal) closeAiModal();
});
- // --- Share Functionality (Firebase) ---
+
+ // --- Share Functionality ---
shareBtn.addEventListener('click', async () => {
- // Original Share icon animation
const originalIcon = shareBtn.innerHTML;
shareBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
shareBtn.disabled = true;
try {
- // Robust Auth Check:
- // 1. If not logged in, try to log in.
- // 2. If login fails (restricted), proceed anyway (swallow error).
- // 3. This allows "Test Mode" (open DB) to work even if Auth is disabled.
if (!auth.currentUser) {
try {
await signInAnonymously(auth);
@@ -554,24 +710,21 @@ int main() {
}
const code = codeInput.value;
- const output = getTerminalContent(); // Capture output
+ const output = getTerminalContent();
const shareId = generateId();
- // Save to Firestore
await setDoc(doc(db, "shares", shareId), {
code: code,
language: 'c',
- output: output, // Store output
+ output: output,
createdAt: new Date().toISOString()
});
- // Generate URL
const url = new URL(window.location.href);
- url.searchParams.delete('code'); // Remove legacy param
+ url.searchParams.delete('code');
url.searchParams.set('share', shareId);
const shareUrl = url.toString();
- // Share or Copy
if (navigator.share) {
try {
await navigator.share({
@@ -580,7 +733,6 @@ int main() {
url: shareUrl
});
} catch (err) {
- // If canceled, still copy to clipboard as fallback
fallbackCopyTextToClipboard(shareUrl);
}
} else {
@@ -588,12 +740,7 @@ int main() {
}
} catch (error) {
console.error("Share failed:", error);
- if (error.code === 'permission-denied') {
- showToast("Share Failed: Access Denied");
- console.log("Tip: Enable Anonymous Auth in Firebase Console OR set Firestore rules to public.");
- } else {
- showToast("Share Failed: " + error.message);
- }
+ showToast("Share Failed");
} finally {
shareBtn.innerHTML = originalIcon;
shareBtn.disabled = false;
@@ -631,8 +778,12 @@ int main() {
document.body.removeChild(textArea);
}
- aiFixBtn.addEventListener('click', () => {
- alert("AI Feature: Coming Soon via Backend Update");
+ // --- Reset Logic ---
+ resetBtn.addEventListener('click', () => {
+ term.reset();
+ currentLine = "";
+ aiSuggestion.classList.remove('visible');
+ term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> ./a.out\r\n`);
});
// Terminal Input
diff --git a/public/cpp.html b/public/cpp.html
@@ -145,6 +145,67 @@
70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
+
+ /* AI Text Overlay */
+ .ai-overlay {
+ position: absolute;
+ bottom: 10px;
+ right: 20px;
+ background: rgba(20, 20, 20, 0.9);
+ border: 1px solid #ef4444;
+ color: #fca5a5;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ z-index: 60;
+ display: none;
+ backdrop-filter: blur(4px);
+ animation: slideIn 0.3s ease-out;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
+ }
+ .ai-overlay.visible { display: flex; align-items: center; gap: 8px; }
+
+ .ai-link {
+ color: #fff;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ text-underline-offset: 4px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: color 0.2s;
+ }
+ .ai-link:hover { color: #bef264; text-decoration-style: solid; }
+
+ @keyframes slideIn {
+ from { transform: translateY(20px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+ }
+
+ /* AI Modal */
+ .modal-overlay {
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
+ background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);
+ z-index: 200; display: none; align-items: center; justify-content: center;
+ }
+ .modal-overlay.open { display: flex; }
+
+ .modal-content {
+ background: #0f0f0f;
+ border: 1px solid var(--accent);
+ width: 90%; max-width: 600px;
+ max-height: 80vh;
+ border-radius: 12px;
+ display: flex; flex-direction: column;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ overflow: hidden;
+ animation: modalPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ @keyframes modalPop {
+ 0% { transform: scale(0.9); opacity: 0; }
+ 100% { transform: scale(1); opacity: 1; }
+ }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -153,13 +214,66 @@
<div class="crt-scanline"></div>
<div id="toast" class="toast">Link Copied to Clipboard!</div>
+ <!-- AI Overlay in Terminal -->
+ <div id="ai-suggestion" class="ai-overlay">
+ <i class="fas fa-exclamation-triangle text-red-500"></i>
+ <span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span>
+ </div>
+
+ <!-- AI Fix Modal -->
+ <div id="ai-modal" class="modal-overlay">
+ <div class="modal-content">
+ <!-- Modal Header -->
+ <div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center">
+ <h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2">
+ <i class="fas fa-robot"></i> AI Diagnosis
+ </h3>
+ <button id="close-modal" class="text-neutral-500 hover:text-white transition-colors">
+ <i class="fas fa-times"></i>
+ </button>
+ </div>
+
+ <!-- Modal Body -->
+ <div class="p-6 overflow-y-auto custom-scrollbar flex-1">
+ <div id="ai-loading" class="hidden flex flex-col items-center justify-center py-8 gap-4">
+ <div class="pulse-dot connected" style="width: 12px; height: 12px;"></div>
+ <p class="font-mono text-neutral-400 text-sm animate-pulse">Analyzing compile error...</p>
+ </div>
+
+ <div id="ai-result" class="hidden space-y-4">
+ <div>
+ <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Explanation</h4>
+ <p id="ai-explanation" class="text-sm text-neutral-200 leading-relaxed font-sans"></p>
+ </div>
+
+ <div>
+ <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Suggested Fix</h4>
+ <div class="relative group">
+ <pre id="ai-code-preview" class="bg-black/30 border border-white/10 p-3 rounded text-xs font-mono text-lime-100 overflow-x-auto custom-scrollbar"></pre>
+ </div>
+ </div>
+ </div>
+
+ <div id="ai-error-msg" class="hidden text-red-400 text-sm font-mono text-center py-4"></div>
+ </div>
+
+ <!-- Modal Footer -->
+ <div class="bg-white/5 px-6 py-4 border-t border-white/10 flex justify-end gap-3">
+ <button id="cancel-fix-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all">Dismiss</button>
+ <button id="apply-fix-btn" disabled class="bg-lime-300/10 hover:bg-lime-300 hover:text-black text-lime-300 border border-lime-300/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
+ <i class="fas fa-check"></i> Apply Fix
+ </button>
+ </div>
+ </div>
+ </div>
+
<!-- Header (Responsive) -->
<header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4">
<!-- Top Row: Title & Status -->
<div class="flex items-center justify-between w-full md:w-auto">
<h1 class="text-lg md:text-xl font-heading font-bold text-white tracking-tight flex items-center">
- <span class="text-lime-300 mr-2">//</span>CPP.COMPILER
+ <span class="text-lime-300 mr-2">//</span>C++.COMPILER
<span id="conn-dot" class="pulse-dot disconnected ml-3" title="Disconnected"></span>
</h1>
@@ -223,14 +337,10 @@ int main() {
}</textarea>
</div>
- <!-- AI Fix Button -->
- <button id="ai-fix-btn" class="hidden absolute bottom-4 right-4 bg-red-500/10 border border-red-500/50 text-red-400 hover:bg-red-500 hover:text-white px-3 py-1.5 rounded-lg text-xs font-mono transition-all items-center gap-2 backdrop-blur-md z-20">
- <i class="fas fa-magic"></i> Fix with AI
- </button>
</div>
<!-- Terminal -->
- <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505]">
+ <div class="flex-1 flex flex-col h-[40%] md:h-auto min-h-0 bg-[#050505] relative">
<div class="bg-black/20 px-4 py-2 text-[10px] font-mono text-lime-300 uppercase border-b border-white/10 flex justify-between items-center shrink-0">
<span>OUT // TERMINAL</span>
<span class="text-neutral-600">XTERM.JS</span>
@@ -282,16 +392,29 @@ int main() {
const resetBtn = document.getElementById('reset-btn');
const shareBtn = document.getElementById('share-btn');
const copyBtn = document.getElementById('copy-btn');
- const aiFixBtn = document.getElementById('ai-fix-btn');
const connDot = document.getElementById('conn-dot');
const connectionStatus = document.getElementById('connection-status');
const codeInput = document.getElementById('code-input');
const toast = document.getElementById('toast');
+
+ // AI Elements
+ const aiSuggestion = document.getElementById('ai-suggestion');
+ const askAiLink = document.getElementById('ask-ai-link');
+ const aiModal = document.getElementById('ai-modal');
+ const closeModal = document.getElementById('close-modal');
+ const cancelFixBtn = document.getElementById('cancel-fix-btn');
+ const applyFixBtn = document.getElementById('apply-fix-btn');
+ const aiLoading = document.getElementById('ai-loading');
+ const aiResult = document.getElementById('ai-result');
+ const aiExplanation = document.getElementById('ai-explanation');
+ const aiCodePreview = document.getElementById('ai-code-preview');
+ const aiErrorMsg = document.getElementById('ai-error-msg');
// --- State ---
let socket = null;
let currentLine = "";
let startTime = 0;
+ let lastSuggestedCode = "";
// --- EDITOR LOGIC (Indentation) ---
codeInput.addEventListener('keydown', function(e) {
@@ -471,8 +594,9 @@ int main() {
if (msg.type === 'stdout') {
let text = msg.data;
+ // Trigger AI Overlay on Error
if (text.toLowerCase().includes("error:") || text.toLowerCase().includes("warning:")) {
- aiFixBtn.classList.remove('hidden');
+ aiSuggestion.classList.add('visible');
}
if (!text.includes('\r')) {
@@ -483,7 +607,13 @@ int main() {
if (msg.msg.includes("Program finished")) {
const duration = (Date.now() - startTime) / 1000;
term.write(`\r\n\x1b[38;2;115;115;115m[Finished in ${duration.toFixed(3)}s]\x1b[0m\r\n`);
+ } else if (msg.msg === "Compilation Failed") {
+ aiSuggestion.classList.add('visible');
}
+ } else if (msg.type === 'ai_response') {
+ handleAiResponse(msg.data);
+ } else if (msg.type === 'ai_error') {
+ showAiError(msg.msg);
}
};
}
@@ -495,7 +625,7 @@ int main() {
document.activeElement.blur();
term.reset();
currentLine = "";
- aiFixBtn.classList.add('hidden');
+ aiSuggestion.classList.remove('visible'); // Hide previous suggestions
let code = codeInput.value;
code = code.replace(/\u00A0/g, " ").replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
@@ -524,10 +654,73 @@ int main() {
term.focus();
});
+ // --- AI Feature Logic ---
+
+ // 1. Open Modal and Request AI Fix
+ askAiLink.addEventListener('click', () => {
+ const errorContext = getTerminalContent();
+ const currentCode = codeInput.value;
+
+ // UI Update
+ aiModal.classList.add('open');
+ aiLoading.classList.remove('hidden');
+ aiResult.classList.add('hidden');
+ aiErrorMsg.classList.add('hidden');
+ applyFixBtn.disabled = true;
+
+ // Send Request
+ socket.send(JSON.stringify({
+ type: 'ai_fix',
+ code: currentCode,
+ error: errorContext,
+ language: 'cpp'
+ }));
+ });
+
+ // 2. Handle AI Response
+ function handleAiResponse(data) {
+ aiLoading.classList.add('hidden');
+ aiResult.classList.remove('hidden');
+
+ aiExplanation.textContent = data.explanation;
+ aiCodePreview.textContent = data.fixed_code;
+ lastSuggestedCode = data.fixed_code;
+
+ applyFixBtn.disabled = false;
+ }
+
+ // 3. Handle AI Error
+ function showAiError(msg) {
+ aiLoading.classList.add('hidden');
+ aiErrorMsg.textContent = msg;
+ aiErrorMsg.classList.remove('hidden');
+ }
+
+ // 4. Apply Fix
+ applyFixBtn.addEventListener('click', () => {
+ if (lastSuggestedCode) {
+ codeInput.value = lastSuggestedCode;
+ closeAiModal();
+ showToast("Fix Applied Successfully!");
+ aiSuggestion.classList.remove('visible');
+ }
+ });
+
+ // 5. Modal Controls
+ function closeAiModal() {
+ aiModal.classList.remove('open');
+ }
+ closeModal.addEventListener('click', closeAiModal);
+ cancelFixBtn.addEventListener('click', closeAiModal);
+ aiModal.addEventListener('click', (e) => {
+ if (e.target === aiModal) closeAiModal();
+ });
+
// --- Reset Logic ---
resetBtn.addEventListener('click', () => {
term.reset();
currentLine = "";
+ aiSuggestion.classList.remove('visible');
term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> ./a.out\r\n`);
});
@@ -624,10 +817,6 @@ int main() {
document.body.removeChild(textArea);
}
- aiFixBtn.addEventListener('click', () => {
- showToast("AI Feature: Coming Soon via Backend Update");
- });
-
// Terminal Input
term.onData((data) => {
if (!socket || socket.readyState !== WebSocket.OPEN) return;
diff --git a/public/docs.html b/public/docs.html
@@ -99,6 +99,7 @@
<a href="#communication" class="nav-link font-mono text-sm">WebSocket Protocol</a>
<a href="#python-engine" class="nav-link font-mono text-sm">Python Engine</a>
<a href="#cpp-engine" class="nav-link font-mono text-sm">C / C++ Engine</a>
+ <a href="#ai-debug" class="nav-link font-mono text-sm text-lime-300">New: AI Auto-Debug</a>
<a href="#sharing" class="nav-link font-mono text-sm">Sharing & Persistence</a>
<a href="#security" class="nav-link font-mono text-sm">Security & Sandbox</a>
<a href="#privacy" class="nav-link font-mono text-sm">Privacy & Data</a>
@@ -110,7 +111,9 @@
<div class="flex flex-wrap gap-2">
<span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">aiohttp</span>
<span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Firebase</span>
- <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">GCC 11</span>
+ <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Gemini AI</span>
+ <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">Render</span>
+ <span class="text-xs px-2 py-1 rounded bg-black border border-white/10 text-lime-300 font-mono">WebSocket</span>
</div>
</div>
</aside>
@@ -238,41 +241,52 @@ def check_and_install_packages(code):
</p>
</section>
- <!-- Sharing & Persistence -->
- <section id="sharing" class="doc-section">
+ <!-- AI Auto-Debug -->
+ <section id="ai-debug" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">05.</span> Sharing & Persistence
+ <span class="mr-3">05.</span> AI Auto-Debugger (New)
</h2>
<p class="text-neutral-300 mb-4">
- The sharing functionality leverages <strong>Firebase Firestore</strong> to provide persistent access to code and execution results. This ensures that sharing a link not only loads the correct source code but also restores the exact terminal state seen at the moment of sharing.
+ The latest version of the compiler includes an intelligent debugging assistant powered by <strong>Google Gemini 2.5 Flash</strong>. This feature automatically detects runtime errors and compilation failures in real-time.
</p>
- <h3 class="text-lg font-bold text-white mt-6 mb-2">How Sharing Works</h3>
+ <h3 class="text-lg font-bold text-white mt-6 mb-2">How it works</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-4">
<div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h4 class="text-white font-bold mb-2">1. Snapshot Creation</h4>
+ <h4 class="text-white font-bold mb-2">1. Error Detection</h4>
<p class="text-sm text-neutral-400 mb-2">
- When the "Share" button is clicked, the system captures:
+ The frontend monitors the terminal output stream for keywords like <code>error:</code>, <code>warning:</code>, or <code>Traceback</code>. When detected, a "Ask AI" prompt appears overlaying the terminal.
</p>
- <ul class="list-disc list-inside text-xs text-neutral-500 space-y-1">
- <li>The complete source code from the editor.</li>
- <li>The current terminal output (stripped of system messages).</li>
- <li>Metadata (language, timestamp).</li>
- </ul>
</div>
<div class="p-6 rounded-xl bg-white/5 border border-white/10">
- <h4 class="text-white font-bold mb-2">2. Storage & Retrieval</h4>
+ <h4 class="text-white font-bold mb-2">2. Contextual Analysis</h4>
<p class="text-sm text-neutral-400 mb-2">
- This data is stored in a Firestore document under a unique 7-character ID.
+ When the user requests help, the system packages:
</p>
<ul class="list-disc list-inside text-xs text-neutral-500 space-y-1">
- <li><strong>Write:</strong> Uses Anonymous Auth to securely write to the DB.</li>
- <li><strong>Read:</strong> The URL `?share=ID` fetches this document.</li>
- <li><strong>Restore:</strong> The editor fills with code, and the terminal replays the saved output.</li>
+ <li>The full source code.</li>
+ <li>The exact error message from the terminal.</li>
+ <li>The language context.</li>
</ul>
</div>
</div>
-
+
+ <div class="p-4 border-l-4 border-lime-300 bg-white/5 mt-4">
+ <p class="text-sm text-neutral-300">
+ <strong>Privacy Note:</strong> Your code is sent to the AI API <em>only</em> when you explicitly click the "Ask AI" link. The data is processed ephemerally and returned as a JSON object containing an explanation and the corrected code block.
+ </p>
+ </div>
+ </section>
+
+ <!-- Sharing & Persistence -->
+ <section id="sharing" class="doc-section">
+ <h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
+ <span class="mr-3">06.</span> Sharing & Persistence
+ </h2>
+ <p class="text-neutral-300 mb-4">
+ The sharing functionality leverages <strong>Firebase Firestore</strong> to provide persistent access to code and execution results. This ensures that sharing a link not only loads the correct source code but also restores the exact terminal state seen at the moment of sharing.
+ </p>
+
<div class="code-block">
<pre><code class="language-javascript">// Data Structure in Firestore
{
@@ -287,7 +301,7 @@ def check_and_install_packages(code):
<!-- Security -->
<section id="security" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">06.</span> Security & Limitations
+ <span class="mr-3">07.</span> Security & Limitations
</h2>
<p class="text-neutral-300 mb-4">
The current implementation runs code directly on the host instance. While effective for a portfolio demonstration or trusted environment, this setup is <strong>not sandboxed</strong>.
@@ -315,7 +329,7 @@ def check_and_install_packages(code):
<!-- Privacy & Data Handling -->
<section id="privacy" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">07.</span> Privacy & Data Handling
+ <span class="mr-3">08.</span> Privacy & Data Handling
</h2>
<p class="text-neutral-300 mb-4">
We believe in complete transparency regarding how your data is handled. As a demonstration platform, we prioritize privacy and ephemeral execution.
@@ -345,7 +359,7 @@ def check_and_install_packages(code):
<!-- Legal & Disclaimer -->
<section id="disclaimer" class="doc-section">
<h2 class="text-2xl font-heading font-bold text-lime-300 mb-6 flex items-center">
- <span class="mr-3">08.</span> Legal & Disclaimer
+ <span class="mr-3">09.</span> Legal & Disclaimer
</h2>
<div class="p-6 rounded-xl border border-red-500/20 bg-red-500/5">
<p class="text-neutral-300 mb-4">
diff --git a/public/index.html b/public/index.html
@@ -250,7 +250,7 @@
<p class="text-neutral-400 max-w-xl mx-auto">A robust platform designed for real development tasks.</p>
</div>
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<!-- Feature 1 -->
<div class="compiler-card p-8" data-aos="fade-up" data-aos-delay="100">
<i class="fas fa-server text-2xl text-lime-300 mb-6"></i>
@@ -269,6 +269,15 @@
<h3 class="text-xl font-bold text-white mb-3">Auto-Pip Install</h3>
<p class="text-sm text-neutral-500 leading-relaxed">The server detects missing Python libraries and installs them on the fly.</p>
</div>
+ <!-- Feature 4 (AI - NEW) -->
+ <div class="compiler-card p-8 border-lime-300/30" data-aos="fade-up" data-aos-delay="400">
+ <div class="flex items-center justify-between mb-6">
+ <i class="fas fa-robot text-2xl text-lime-300"></i>
+ <span class="text-[10px] bg-lime-300 text-black px-2 py-0.5 rounded font-bold uppercase">New</span>
+ </div>
+ <h3 class="text-xl font-bold text-white mb-3">AI Auto-Debug</h3>
+ <p class="text-sm text-neutral-500 leading-relaxed">Intelligent error detection and real-time code fixes powered by Gemini AI.</p>
+ </div>
</div>
</div>
</section>
diff --git a/public/python.html b/public/python.html
@@ -145,6 +145,67 @@
70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
+
+ /* AI Text Overlay */
+ .ai-overlay {
+ position: absolute;
+ bottom: 10px;
+ right: 20px;
+ background: rgba(20, 20, 20, 0.9);
+ border: 1px solid #ef4444;
+ color: #fca5a5;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ z-index: 60;
+ display: none;
+ backdrop-filter: blur(4px);
+ animation: slideIn 0.3s ease-out;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
+ }
+ .ai-overlay.visible { display: flex; align-items: center; gap: 8px; }
+
+ .ai-link {
+ color: #fff;
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ text-underline-offset: 4px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: color 0.2s;
+ }
+ .ai-link:hover { color: #bef264; text-decoration-style: solid; }
+
+ @keyframes slideIn {
+ from { transform: translateY(20px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+ }
+
+ /* AI Modal */
+ .modal-overlay {
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
+ background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);
+ z-index: 200; display: none; align-items: center; justify-content: center;
+ }
+ .modal-overlay.open { display: flex; }
+
+ .modal-content {
+ background: #0f0f0f;
+ border: 1px solid var(--accent);
+ width: 90%; max-width: 600px;
+ max-height: 80vh;
+ border-radius: 12px;
+ display: flex; flex-direction: column;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+ overflow: hidden;
+ animation: modalPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+ }
+
+ @keyframes modalPop {
+ 0% { transform: scale(0.9); opacity: 0; }
+ 100% { transform: scale(1); opacity: 1; }
+ }
</style>
</head>
<body class="antialiased selection:bg-lime-300 selection:text-black">
@@ -153,6 +214,59 @@
<div class="crt-scanline"></div>
<div id="toast" class="toast">Link Copied to Clipboard!</div>
+ <!-- AI Overlay in Terminal -->
+ <div id="ai-suggestion" class="ai-overlay">
+ <i class="fas fa-exclamation-triangle text-red-500"></i>
+ <span>Error detected. <span id="ask-ai-link" class="ai-link">Ask AI</span> to fix it.</span>
+ </div>
+
+ <!-- AI Fix Modal -->
+ <div id="ai-modal" class="modal-overlay">
+ <div class="modal-content">
+ <!-- Modal Header -->
+ <div class="bg-white/5 px-6 py-4 border-b border-white/10 flex justify-between items-center">
+ <h3 class="font-heading font-bold text-lg text-lime-300 flex items-center gap-2">
+ <i class="fas fa-robot"></i> AI Diagnosis
+ </h3>
+ <button id="close-modal" class="text-neutral-500 hover:text-white transition-colors">
+ <i class="fas fa-times"></i>
+ </button>
+ </div>
+
+ <!-- Modal Body -->
+ <div class="p-6 overflow-y-auto custom-scrollbar flex-1">
+ <div id="ai-loading" class="hidden flex flex-col items-center justify-center py-8 gap-4">
+ <div class="pulse-dot connected" style="width: 12px; height: 12px;"></div>
+ <p class="font-mono text-neutral-400 text-sm animate-pulse">Analyzing compile error...</p>
+ </div>
+
+ <div id="ai-result" class="hidden space-y-4">
+ <div>
+ <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Explanation</h4>
+ <p id="ai-explanation" class="text-sm text-neutral-200 leading-relaxed font-sans"></p>
+ </div>
+
+ <div>
+ <h4 class="font-mono text-xs uppercase text-neutral-500 mb-2">Suggested Fix</h4>
+ <div class="relative group">
+ <pre id="ai-code-preview" class="bg-black/30 border border-white/10 p-3 rounded text-xs font-mono text-lime-100 overflow-x-auto custom-scrollbar"></pre>
+ </div>
+ </div>
+ </div>
+
+ <div id="ai-error-msg" class="hidden text-red-400 text-sm font-mono text-center py-4"></div>
+ </div>
+
+ <!-- Modal Footer -->
+ <div class="bg-white/5 px-6 py-4 border-t border-white/10 flex justify-end gap-3">
+ <button id="cancel-fix-btn" class="px-4 py-2 rounded text-xs font-mono font-bold uppercase text-neutral-400 hover:text-white hover:bg-white/10 transition-all">Dismiss</button>
+ <button id="apply-fix-btn" disabled class="bg-lime-300/10 hover:bg-lime-300 hover:text-black text-lime-300 border border-lime-300/20 px-4 py-2 rounded text-xs font-mono font-bold uppercase transition-all flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
+ <i class="fas fa-check"></i> Apply Fix
+ </button>
+ </div>
+ </div>
+ </div>
+
<!-- Header (Responsive) -->
<header class="h-auto md:h-16 flex flex-col md:flex-row items-center justify-between px-4 py-3 md:px-6 shrink-0 border-b border-white/10 z-20 bg-neutral-900/50 backdrop-blur-md gap-3 md:gap-4">
@@ -214,10 +328,6 @@ print(time.strftime("%H:%M:%S"))
print("Done.")</textarea>
</div>
- <!-- AI Fix Button -->
- <button id="ai-fix-btn" class="hidden absolute bottom-4 right-4 bg-red-500/10 border border-red-500/50 text-red-400 hover:bg-red-500 hover:text-white px-3 py-1.5 rounded-lg text-xs font-mono transition-all items-center gap-2 backdrop-blur-md z-20">
- <i class="fas fa-magic"></i> Fix with AI
- </button>
</div>
<!-- Terminal -->
@@ -273,16 +383,29 @@ print("Done.")</textarea>
const resetBtn = document.getElementById('reset-btn');
const shareBtn = document.getElementById('share-btn');
const copyBtn = document.getElementById('copy-btn');
- const aiFixBtn = document.getElementById('ai-fix-btn');
const connDot = document.getElementById('conn-dot');
const connectionStatus = document.getElementById('connection-status');
const codeInput = document.getElementById('code-input');
const toast = document.getElementById('toast');
+
+ // AI Elements
+ const aiSuggestion = document.getElementById('ai-suggestion');
+ const askAiLink = document.getElementById('ask-ai-link');
+ const aiModal = document.getElementById('ai-modal');
+ const closeModal = document.getElementById('close-modal');
+ const cancelFixBtn = document.getElementById('cancel-fix-btn');
+ const applyFixBtn = document.getElementById('apply-fix-btn');
+ const aiLoading = document.getElementById('ai-loading');
+ const aiResult = document.getElementById('ai-result');
+ const aiExplanation = document.getElementById('ai-explanation');
+ const aiCodePreview = document.getElementById('ai-code-preview');
+ const aiErrorMsg = document.getElementById('ai-error-msg');
// --- State ---
let socket = null;
let currentLine = "";
let startTime = 0;
+ let lastSuggestedCode = "";
// --- EDITOR LOGIC (Indentation for Python) ---
codeInput.addEventListener('keydown', function(e) {
@@ -453,8 +576,10 @@ print("Done.")</textarea>
if (msg.type === 'stdout') {
let text = msg.data;
+ // Trigger AI Overlay on Error
+ // Python errors: "Traceback (most recent call last):", "SyntaxError:", "NameError:", etc.
if (text.toLowerCase().includes("error") || text.toLowerCase().includes("traceback")) {
- aiFixBtn.classList.remove('hidden');
+ aiSuggestion.classList.add('visible');
}
if (!text.includes('\r')) {
@@ -466,6 +591,10 @@ print("Done.")</textarea>
const duration = (Date.now() - startTime) / 1000;
term.write(`\r\n\x1b[38;2;115;115;115m[Finished in ${duration.toFixed(3)}s]\x1b[0m\r\n`);
}
+ } else if (msg.type === 'ai_response') {
+ handleAiResponse(msg.data);
+ } else if (msg.type === 'ai_error') {
+ showAiError(msg.msg);
}
};
}
@@ -477,7 +606,7 @@ print("Done.")</textarea>
document.activeElement.blur();
term.reset();
currentLine = "";
- aiFixBtn.classList.add('hidden');
+ aiSuggestion.classList.remove('visible'); // Hide previous suggestions
let code = codeInput.value;
// Basic sanitization
@@ -495,10 +624,73 @@ print("Done.")</textarea>
term.focus();
});
+ // --- AI Feature Logic ---
+
+ // 1. Open Modal and Request AI Fix
+ askAiLink.addEventListener('click', () => {
+ const errorContext = getTerminalContent();
+ const currentCode = codeInput.value;
+
+ // UI Update
+ aiModal.classList.add('open');
+ aiLoading.classList.remove('hidden');
+ aiResult.classList.add('hidden');
+ aiErrorMsg.classList.add('hidden');
+ applyFixBtn.disabled = true;
+
+ // Send Request
+ socket.send(JSON.stringify({
+ type: 'ai_fix',
+ code: currentCode,
+ error: errorContext,
+ language: 'python'
+ }));
+ });
+
+ // 2. Handle AI Response
+ function handleAiResponse(data) {
+ aiLoading.classList.add('hidden');
+ aiResult.classList.remove('hidden');
+
+ aiExplanation.textContent = data.explanation;
+ aiCodePreview.textContent = data.fixed_code;
+ lastSuggestedCode = data.fixed_code;
+
+ applyFixBtn.disabled = false;
+ }
+
+ // 3. Handle AI Error
+ function showAiError(msg) {
+ aiLoading.classList.add('hidden');
+ aiErrorMsg.textContent = msg;
+ aiErrorMsg.classList.remove('hidden');
+ }
+
+ // 4. Apply Fix
+ applyFixBtn.addEventListener('click', () => {
+ if (lastSuggestedCode) {
+ codeInput.value = lastSuggestedCode;
+ closeAiModal();
+ showToast("Fix Applied Successfully!");
+ aiSuggestion.classList.remove('visible');
+ }
+ });
+
+ // 5. Modal Controls
+ function closeAiModal() {
+ aiModal.classList.remove('open');
+ }
+ closeModal.addEventListener('click', closeAiModal);
+ cancelFixBtn.addEventListener('click', closeAiModal);
+ aiModal.addEventListener('click', (e) => {
+ if (e.target === aiModal) closeAiModal();
+ });
+
// --- Reset Logic ---
resetBtn.addEventListener('click', () => {
term.reset();
currentLine = "";
+ aiSuggestion.classList.remove('visible');
term.write(`\x1b[38;2;190;242;100m> CONNECTION ESTABLISHED.\x1b[0m\r\n> python3 main.py\r\n`);
});
@@ -595,10 +787,6 @@ print("Done.")</textarea>
document.body.removeChild(textArea);
}
- aiFixBtn.addEventListener('click', () => {
- showToast("AI Feature: Coming Soon via Backend Update");
- });
-
// Terminal Input
term.onData((data) => {
if (!socket || socket.readyState !== WebSocket.OPEN) return;