commit 4ce573652822b4f4a6a92dd3457910b152519e1b
parent d768de752036576b0812b6a46e9e7515fdc8761f
Author: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Date: Sat, 17 Jan 2026 16:31:49 +0000
Update backend with improved authentication, model upgrade, and error handling
Co-authored-by: notamitgamer <199483577+notamitgamer@users.noreply.github.com>
Diffstat:
| A | .env.example | | | 16 | ++++++++++++++++ |
| M | backend.py | | | 86 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
| A | index.html | | | 552 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | requirements.txt | | | 12 | ++++++------ |
4 files changed, 643 insertions(+), 23 deletions(-)
diff --git a/.env.example b/.env.example
@@ -0,0 +1,16 @@
+# Gemini API Configuration
+GEMINI_API_KEY=your_gemini_api_key_here
+
+# Firebase Admin SDK Configuration
+# Option 1: Use a JSON file (for local development)
+# Place your firebase-adminsdk.json file in the root directory
+
+# Option 2: Use environment variable with JSON content (for Render.com deployment)
+# FIREBASE_CREDENTIALS={"type":"service_account","project_id":"your-project-id",...}
+
+# Option 3: Use Application Default Credentials
+# Set GOOGLE_APPLICATION_CREDENTIALS to the path of your service account JSON file
+# GOOGLE_APPLICATION_CREDENTIALS=/path/to/firebase-adminsdk.json
+
+# Server Configuration
+PORT=5000
diff --git a/backend.py b/backend.py
@@ -18,17 +18,30 @@ CORS(app, resources={r"/api/*": {"origins": ["https://amit.is-a.dev", "http://12
# Otherwise, use a service account JSON file.
try:
if not firebase_admin._apps:
- # Check for service account file, otherwise assume env vars or default creds
- if os.path.exists("firebase-adminsdk.json"):
+ # Check for FIREBASE_CREDENTIALS environment variable (JSON string)
+ firebase_creds_json = os.environ.get("FIREBASE_CREDENTIALS")
+
+ if firebase_creds_json:
+ # Parse JSON string from environment variable
+ cred_dict = json.loads(firebase_creds_json)
+ cred = credentials.Certificate(cred_dict)
+ print("✅ Using Firebase credentials from FIREBASE_CREDENTIALS environment variable")
+ elif os.path.exists("firebase-adminsdk.json"):
cred = credentials.Certificate("firebase-adminsdk.json")
+ print("✅ Using Firebase credentials from firebase-adminsdk.json file")
else:
+ # Try application default credentials
cred = credentials.ApplicationDefault()
+ print("✅ Using Firebase Application Default Credentials")
firebase_admin.initialize_app(cred)
- print("Firebase Admin Initialized")
+ print("✅ Firebase Admin SDK initialized successfully")
db = firestore.client()
+ print("✅ Firestore client initialized successfully")
except Exception as e:
- print(f"Firebase Init Warning: {e}")
+ print(f"❌ Firebase initialization error: {type(e).__name__}: {e}")
+ print("⚠️ API will run but Firestore features will not work")
+ db = None
# 2. Gemini Init
GENAI_API_KEY = os.environ.get("GEMINI_API_KEY")
@@ -64,7 +77,7 @@ STRICT RULES:
"""
model = genai.GenerativeModel(
- model_name="gemini-1.5-flash",
+ model_name="models/gemini-2.5-flash-preview-09-2025",
generation_config=generation_config,
system_instruction=SYSTEM_PROMPT,
)
@@ -74,15 +87,34 @@ def verify_token(auth_header):
"""
Verifies the Firebase ID Token sent from the client.
Returns user_uid if valid, None otherwise.
+ Enhanced with better error messages and logging.
"""
- if not auth_header or not auth_header.startswith("Bearer "):
+ if not auth_header:
+ print("❌ No Authorization header provided")
+ return None
+
+ if not auth_header.startswith("Bearer "):
+ print("❌ Invalid Authorization header format (must start with 'Bearer ')")
return None
+
token = auth_header.split("Bearer ")[1]
+
try:
decoded_token = auth.verify_id_token(token)
- return decoded_token['uid']
+ user_id = decoded_token['uid']
+ print(f"✅ Token verified for user: {user_id}")
+ return user_id
+ except auth.InvalidIdTokenError:
+ print("❌ Invalid ID token - token is malformed or invalid")
+ return None
+ except auth.ExpiredIdTokenError:
+ print("❌ Token has expired - user needs to refresh their token")
+ return None
+ except auth.RevokedIdTokenError:
+ print("❌ Token has been revoked")
+ return None
except Exception as e:
- print(f"Token verification failed: {e}")
+ print(f"❌ Token verification error: {type(e).__name__}: {e}")
return None
# --- ROUTES ---
@@ -97,16 +129,20 @@ def generate_title():
# Verify auth even for titles to prevent abuse
user_uid = verify_token(request.headers.get('Authorization'))
if not user_uid:
- return jsonify({"error": "Unauthorized"}), 401
+ return jsonify({
+ "error": "Unauthorized",
+ "message": "Invalid or missing authentication token. Please sign in again."
+ }), 401
data = request.json
message = data.get('message', '')
try:
- title_model = genai.GenerativeModel("gemini-1.5-flash")
+ title_model = genai.GenerativeModel("models/gemini-2.5-flash-preview-09-2025")
res = title_model.generate_content(f"Summarize this coding query into a 3-5 word title: '{message}'")
return jsonify({"title": res.text.strip()})
- except:
+ except Exception as e:
+ print(f"❌ Error generating title: {type(e).__name__}: {e}")
return jsonify({"title": "New Chat"})
@app.route('/api/chat', methods=['POST'])
@@ -119,7 +155,10 @@ def chat():
# 1. Verify User
user_uid = verify_token(request.headers.get('Authorization'))
if not user_uid:
- return jsonify({"error": "Unauthorized"}), 401
+ return jsonify({
+ "error": "Unauthorized",
+ "message": "Invalid or missing authentication token. Please sign in again."
+ }), 401
data = request.json
user_msg = data.get('message')
@@ -129,7 +168,10 @@ def chat():
session_id = data.get('sessionId')
if not user_msg:
- return jsonify({"error": "Empty message"}), 400
+ return jsonify({
+ "error": "Bad Request",
+ "message": "Message cannot be empty"
+ }), 400
# 2. Construct Prompt with Context
context_str = ""
@@ -145,7 +187,14 @@ def chat():
chat_history.append({"role": "user", "parts": [turn.get('user', '')]})
chat_history.append({"role": "model", "parts": [turn.get('model', '')]})
- chat_session = model.start_chat(history=chat_history)
+ try:
+ chat_session = model.start_chat(history=chat_history)
+ except Exception as e:
+ print(f"❌ Error starting chat session: {type(e).__name__}: {e}")
+ return jsonify({
+ "error": "Internal Server Error",
+ "message": "Failed to initialize chat session"
+ }), 500
# 3. Stream Response
def generate():
@@ -163,7 +212,7 @@ def chat():
# 4. Save Interaction to Firestore
# We save this asynchronously (conceptually) after streaming is done.
- if session_id:
+ if session_id and db:
try:
doc_ref = db.collection('users').document(user_uid).collection('chats').document(session_id)
@@ -192,11 +241,14 @@ def chat():
"messages": firestore.ArrayUnion([msg_data, ai_data]),
"updatedAt": datetime.datetime.now(datetime.timezone.utc)
})
+ print(f"✅ Chat saved to Firestore for session: {session_id}")
except Exception as db_err:
- print(f"Database Save Error: {db_err}")
+ print(f"❌ Database Save Error: {type(db_err).__name__}: {db_err}")
except Exception as e:
- yield f"Error: {str(e)}"
+ error_msg = f"❌ Gemini API Error: {type(e).__name__}: {str(e)}"
+ print(error_msg)
+ yield f"\n\nError: I encountered an issue while processing your request. Please try again."
return Response(stream_with_context(generate()), mimetype='text/plain')
diff --git a/index.html b/index.html
@@ -0,0 +1,551 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+ <!-- SEO & Verification -->
+ <meta name="google-site-verification" content="pwteHw44Ax3N2B1zkFzVJB7NIK0-xgQGRihK_vtZTF4" />
+ <link rel="canonical" href="https://ada.amit.is-a.dev/" />
+ <link rel="icon" type="image/x-icon" href="favicon-32x32.png" >
+
+ <meta name="description" content="Ada Aranag - Your intelligent assistant for quick answers and engaging conversations. Specialized in Code Analysis and Generation.">
+ <meta name="keywords" content="Ada Aranag, Ada, AI, chatbot, assistant, intelligent, conversation, Ada AI, coding, programming, python, javascript, debugger">
+ <meta name="author" content="Amit Dutta">
+
+ <!-- Open Graph / Facebook Meta Tags -->
+ <meta property="og:title" content="Ada // AI Coding Environment">
+ <meta property="og:description" content="Your intelligent assistant for quick answers and engaging conversations. Specialized in Code Analysis and Generation.">
+ <meta property="og:image" content="https://ada.amit.is-a.dev/logo.png">
+ <meta property="og:url" content="https://ada.amit.is-a.dev/">
+ <meta property="og:type" content="website">
+ <meta property="og:site_name" content="Ada Aranag">
+
+ <!-- Twitter Card Meta Tags -->
+ <meta name="twitter:card" content="summary_large_image">
+ <meta name="twitter:title" content="Ada // AI Coding Environment">
+ <meta name="twitter:description" content="Your intelligent assistant for quick answers and engaging conversations. Specialized in Code Analysis.">
+ <meta name="twitter:image" content="https://ada.amit.is-a.dev/logo.png">
+
+ <!-- Security Headers (Attempt to fix Popup Warning) -->
+ <meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin-allow-popups">
+
+ <!-- Schema.org JSON-LD -->
+ <script type="application/ld+json">
+ {
+ "@context": "https://schema.org",
+ "@type": "Website",
+ "name": "Ada Aranag",
+ "alternateName":["Ada Aranag","Ada AI Chatbot","AI Assistant","Intelligent Chatbot","Ada"],
+ "url": "https://ada.amit.is-a.dev"
+ }
+ </script>
+
+ <title>Ada // AI Coding Environment</title>
+
+ <!-- Tailwind CSS -->
+ <script src="https://cdn.tailwindcss.com"></script>
+
+ <!-- Fonts -->
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
+
+ <!-- Libraries -->
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js"></script>
+
+ <!-- CodeMirror -->
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/dracula.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/clike/clike.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
+
+ <style>
+ :root {
+ --bg-main: #050505;
+ --accent: #bef264; /* Lime 300 */
+ --border: rgba(255,255,255,0.1);
+ --glass: rgba(20, 20, 20, 0.8);
+ }
+ body { background: var(--bg-main); color: #e5e5e5; font-family: 'Inter', sans-serif; overflow: hidden; }
+ .font-mono { font-family: 'JetBrains Mono', monospace; }
+
+ /* Scrollbar */
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
+ ::-webkit-scrollbar-track { background: var(--bg-main); }
+ ::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
+ ::-webkit-scrollbar-thumb:hover { background: var(--accent); }
+
+ /* CodeMirror Overrides */
+ .CodeMirror { height: 100% !important; font-family: 'JetBrains Mono', monospace; font-size: 13px; background: #0f0f10 !important; }
+ .cm-s-dracula.CodeMirror { background: #0f0f10 !important; }
+
+ /* Markdown Styling */
+ .prose p { margin-bottom: 0.8rem; line-height: 1.6; }
+ .prose h1, .prose h2, .prose h3 { color: var(--accent); font-weight: bold; margin-top: 1rem; margin-bottom: 0.5rem; }
+ .prose code { color: var(--accent); background: rgba(190, 242, 100, 0.1); padding: 2px 4px; border-radius: 4px; font-family: 'JetBrains Mono'; font-size: 0.85em; }
+ .prose pre { background: #111; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; border: 1px solid var(--border); margin-bottom: 1rem; }
+ .prose pre code { background: transparent; padding: 0; color: #ccc; }
+ .prose ul { list-style-type: disc; padding-left: 1.5rem; margin-bottom: 1rem; }
+ .prose ol { list-style-type: decimal; padding-left: 1.5rem; margin-bottom: 1rem; }
+
+ /* Utilities */
+ .glass-panel { background: var(--glass); backdrop-filter: blur(12px); border-right: 1px solid var(--border); }
+ .loader { border: 2px solid #333; border-top: 2px solid var(--accent); border-radius: 50%; width: 16px; height: 16px; animation: spin 1s linear infinite; }
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
+
+ /* Mobile Tabs */
+ .tab-btn.active { border-bottom: 2px solid var(--accent); color: var(--accent); }
+ </style>
+</head>
+<body class="h-screen w-screen flex flex-col md:flex-row relative">
+
+ <!-- LOGIN MODAL -->
+ <div id="auth-modal" class="fixed inset-0 z-50 bg-black/90 flex items-center justify-center backdrop-blur-sm hidden">
+ <div class="bg-neutral-900 border border-neutral-700 p-8 rounded-xl max-w-md w-full shadow-2xl relative overflow-hidden">
+ <div class="absolute top-0 left-0 w-full h-1 bg-lime-300"></div>
+ <h2 class="text-2xl font-bold text-white mb-2 font-mono">System Login</h2>
+ <p class="text-neutral-400 mb-6 text-sm">Authenticate to access Ada Coding Environment.</p>
+
+ <!-- Google Login -->
+ <button onclick="handleGoogleLogin()" class="w-full flex items-center justify-center gap-3 bg-white text-black py-3 rounded font-bold hover:bg-gray-200 transition-colors mb-6">
+ <svg class="w-5 h-5" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.84z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>
+ Sign in with Google
+ </button>
+
+ <!-- Divider -->
+ <div class="relative mb-6">
+ <div class="absolute inset-0 flex items-center"><div class="w-full border-t border-white/10"></div></div>
+ <div class="relative flex justify-center text-xs uppercase"><span class="bg-neutral-900 px-2 text-neutral-500">Or continue with email</span></div>
+ </div>
+
+ <!-- Email/Pass Inputs -->
+ <div class="space-y-4 mb-4">
+ <input type="email" id="email-input" placeholder="Email Address" class="w-full bg-neutral-800 border border-neutral-700 rounded p-3 text-white focus:border-lime-300 outline-none transition-colors placeholder-neutral-500 font-mono text-sm">
+ <input type="password" id="password-input" placeholder="Password" class="w-full bg-neutral-800 border border-neutral-700 rounded p-3 text-white focus:border-lime-300 outline-none transition-colors placeholder-neutral-500 font-mono text-sm">
+ </div>
+
+ <!-- Email Buttons -->
+ <div class="flex gap-3">
+ <button onclick="handleEmailLogin()" class="flex-1 bg-lime-300 text-black py-3 rounded font-bold hover:bg-lime-400 transition-colors text-sm font-mono uppercase">Login</button>
+ <button onclick="handleEmailSignUp()" class="flex-1 bg-transparent border border-neutral-600 text-white py-3 rounded font-bold hover:border-lime-300 hover:text-lime-300 transition-colors text-sm font-mono uppercase">Sign Up</button>
+ </div>
+
+ <div class="text-center text-xs text-neutral-500 font-mono mt-6">SECURE CONNECTION // FIREBASE AUTH</div>
+ </div>
+ </div>
+
+ <!-- MAIN UI -->
+
+ <!-- LEFT PANE: SIDEBAR + CHAT -->
+ <div id="left-pane" class="w-full md:w-[40%] flex flex-col h-full border-r border-white/10 relative bg-neutral-900/50">
+
+ <!-- HEADER -->
+ <div class="h-14 border-b border-white/10 flex items-center justify-between px-4 bg-neutral-900">
+ <div class="flex items-center gap-2">
+ <div class="w-2 h-2 bg-lime-300 rounded-full animate-pulse"></div>
+ <span class="font-mono font-bold text-lime-300 tracking-wider">ADA // AI</span>
+ </div>
+ <div class="flex items-center gap-3">
+ <button onclick="toggleSidebar()" class="md:hidden text-neutral-400 hover:text-white">
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
+ </button>
+ <div id="user-profile" class="hidden md:flex items-center gap-2 cursor-pointer" onclick="signOutUser()">
+ <img id="user-avatar" src="" class="w-6 h-6 rounded-full border border-lime-300/30">
+ </div>
+ </div>
+ </div>
+
+ <!-- HISTORY SIDEBAR (Off-canvas on mobile) -->
+ <div id="sidebar" class="absolute inset-y-0 left-0 w-64 bg-black border-r border-white/10 transform -translate-x-full transition-transform duration-300 z-40 md:relative md:translate-x-0 md:w-0 md:border-none md:hidden overflow-hidden">
+ <!-- Populated via JS -->
+ </div>
+
+ <!-- CHAT AREA -->
+ <div id="chat-container" class="flex-1 overflow-y-auto p-4 space-y-6 relative">
+ <!-- Welcome Message -->
+ <div class="flex gap-4">
+ <div class="w-8 h-8 rounded bg-lime-300/10 flex items-center justify-center border border-lime-300/20 text-lime-300 text-xs font-mono">AI</div>
+ <div class="flex-1">
+ <div class="text-sm text-lime-300 font-mono mb-1">Ada</div>
+ <div class="text-sm text-neutral-300 leading-relaxed prose">
+ <p>Systems Online. Ready for code analysis.</p>
+ <p>Paste code, upload a file, or ask a question.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- FILE UPLOAD PREVIEW -->
+ <div id="file-preview" class="hidden px-4 py-2 bg-neutral-800 border-t border-white/5 flex items-center justify-between">
+ <div class="flex items-center gap-2 text-xs font-mono text-lime-300">
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
+ <span id="file-name">code.py</span>
+ </div>
+ <button onclick="clearFile()" class="text-neutral-500 hover:text-white">×</button>
+ </div>
+
+ <!-- INPUT AREA -->
+ <div class="p-4 bg-neutral-900 border-t border-white/10">
+ <div class="relative flex items-end gap-2 bg-neutral-800 p-2 rounded-lg border border-white/5 focus-within:border-lime-300/50 transition-colors">
+ <input type="file" id="file-upload" class="hidden" onchange="handleFileUpload(this)">
+ <button onclick="document.getElementById('file-upload').click()" class="p-2 text-neutral-400 hover:text-lime-300 transition-colors" title="Upload Context">
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
+ </button>
+ <textarea id="chat-input" rows="1" class="flex-1 bg-transparent text-sm text-white focus:outline-none resize-none py-2 font-mono max-h-32" placeholder="Ask about code..."></textarea>
+ <button onclick="sendMessage()" id="send-btn" class="p-2 bg-lime-300 text-black rounded hover:bg-white transition-colors">
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <!-- RIGHT PANE: CODE EDITOR -->
+ <div id="right-pane" class="hidden md:flex flex-1 flex-col h-full bg-[#0f0f10] relative">
+ <!-- Editor Header -->
+ <div class="h-14 border-b border-white/10 flex items-center justify-between px-4 bg-[#0f0f10]">
+ <div class="flex items-center gap-3">
+ <div class="text-xs font-mono text-neutral-500 uppercase tracking-wider">Editor Canvas</div>
+ <div id="temp-code-badge" class="hidden px-2 py-0.5 bg-lime-300/10 border border-lime-300/30 text-lime-300 text-[10px] rounded font-mono">temp_code loaded</div>
+ </div>
+ <div class="flex items-center gap-3">
+ <select id="lang-select" onchange="changeLanguage()" class="bg-neutral-800 text-xs text-white border border-neutral-700 rounded px-2 py-1 outline-none">
+ <option value="python">Python</option>
+ <option value="javascript">JavaScript</option>
+ <option value="clike">C / C++ / Java</option>
+ </select>
+ <button onclick="runCode()" class="flex items-center gap-2 px-3 py-1.5 bg-neutral-800 hover:bg-lime-300 hover:text-black border border-neutral-700 rounded text-xs font-mono uppercase transition-colors">
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
+ Run / Compile
+ </button>
+ </div>
+ </div>
+
+ <!-- Editor Container -->
+ <div class="flex-1 relative overflow-hidden">
+ <textarea id="code-editor"></textarea>
+ </div>
+ </div>
+
+ <!-- MOBILE TABS -->
+ <div class="md:hidden fixed bottom-0 left-0 w-full bg-neutral-900 border-t border-white/10 flex z-30">
+ <button onclick="switchTab('chat')" id="tab-chat" class="flex-1 py-3 text-center text-xs font-mono uppercase tab-btn active">Chat</button>
+ <button onclick="switchTab('code')" id="tab-code" class="flex-1 py-3 text-center text-xs font-mono uppercase tab-btn text-neutral-500">Code Canvas</button>
+ </div>
+
+ <!-- JS LOGIC: Converted to Modules to prevent 'firebase is not defined' -->
+ <script type="module">
+ import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
+ import { getAuth, GoogleAuthProvider, signInWithPopup, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";
+ import { getFirestore } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore.js";
+
+ // --- CONFIG ---
+ const firebaseConfig = {
+ apiKey: "AIzaSyAhyoNYUSXWIxdBiTxwSDcU7HkhhLEVlOc",
+ authDomain: "ada-ai-aranag.firebaseapp.com",
+ projectId: "ada-ai-aranag",
+ storageBucket: "ada-ai-aranag.firebasestorage.app",
+ messagingSenderId: "865895114061",
+ appId: "1:865895114061:web:5fd4493b0e388727e6c304"
+ };
+
+ // --- INIT FIREBASE ---
+ const app = initializeApp(firebaseConfig);
+ const auth = getAuth(app);
+ const db = getFirestore(app);
+
+ let currentUser = null;
+ let currentSessionId = null;
+
+ // --- INIT EDITOR ---
+ // CodeMirror is global (loaded via script tags in head)
+ const editor = CodeMirror.fromTextArea(document.getElementById("code-editor"), {
+ lineNumbers: true,
+ mode: "python",
+ theme: "dracula",
+ lineWrapping: true,
+ indentUnit: 4
+ });
+
+ window.addEventListener('resize', () => editor.refresh());
+
+ // --- AUTH LOGIC ---
+ onAuthStateChanged(auth, (user) => {
+ if (user) {
+ currentUser = user;
+ document.getElementById('auth-modal').classList.add('hidden');
+ document.getElementById('user-avatar').src = user.photoURL || 'https://ui-avatars.com/api/?name=User';
+ initSession();
+ loadHistory();
+ } else {
+ document.getElementById('auth-modal').classList.remove('hidden');
+ }
+ });
+
+ // --- EXPORTED FUNCTIONS (for HTML onclick) ---
+ window.handleGoogleLogin = function() {
+ const provider = new GoogleAuthProvider();
+ signInWithPopup(auth, provider).catch(e => alert(e.message));
+ }
+
+ window.handleEmailLogin = function() {
+ const email = document.getElementById('email-input').value;
+ const pass = document.getElementById('password-input').value;
+ if(!email || !pass) return alert("Please enter email and password");
+ signInWithEmailAndPassword(auth, email, pass).catch(e => alert(e.message));
+ }
+
+ window.handleEmailSignUp = function() {
+ const email = document.getElementById('email-input').value;
+ const pass = document.getElementById('password-input').value;
+ if(!email || !pass) return alert("Please enter email and password");
+ createUserWithEmailAndPassword(auth, email, pass).catch(e => alert(e.message));
+ }
+
+ window.signOutUser = function() {
+ if(confirm("Sign out?")) signOut(auth);
+ }
+
+ // --- SESSION & LZSTRING ---
+ function initSession() {
+ const urlParams = new URLSearchParams(window.location.search);
+ const lzCode = urlParams.get('code');
+ if (lzCode) {
+ try {
+ const decoded = LZString.decompressFromEncodedURIComponent(lzCode);
+ editor.setValue(decoded);
+ document.getElementById('temp-code-badge').classList.remove('hidden');
+ } catch (e) { console.error("LZString Decode Error", e); }
+ }
+ if (!currentSessionId) {
+ currentSessionId = crypto.randomUUID();
+ }
+ }
+
+ // --- FILE UPLOAD ---
+ let fileContextContent = "";
+
+ window.handleFileUpload = function(input) {
+ const file = input.files[0];
+ if (!file) return;
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ fileContextContent = e.target.result;
+ document.getElementById('file-preview').classList.remove('hidden');
+ document.getElementById('file-name').innerText = file.name;
+
+ if(file.name.endsWith('.py')) changeLanguage('python');
+ if(file.name.endsWith('.js')) changeLanguage('javascript');
+ if(file.name.endsWith('.c') || file.name.endsWith('.cpp')) changeLanguage('clike');
+ };
+ reader.readAsText(file);
+ }
+
+ window.clearFile = function() {
+ fileContextContent = "";
+ document.getElementById('file-upload').value = "";
+ document.getElementById('file-preview').classList.add('hidden');
+ }
+
+ // --- CHAT LOGIC ---
+ const chatContainer = document.getElementById('chat-container');
+ const chatInput = document.getElementById('chat-input');
+ let chatHistory = [];
+
+ chatInput.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ window.sendMessage();
+ }
+ });
+
+ window.sendMessage = async function() {
+ const text = chatInput.value.trim();
+ if (!text && !fileContextContent) return;
+ if (!currentUser) {
+ alert("Please login first.");
+ return;
+ }
+
+ appendMessage('user', text);
+ chatInput.value = "";
+ chatInput.style.height = 'auto';
+
+ const codeContext = editor.getValue();
+ // UI: AI Loading
+ const loadingId = appendLoading();
+
+ try {
+ // Get fresh token
+ const token = await currentUser.getIdToken();
+
+ // Render Backend URL
+ const API_URL = 'https://ada-web.onrender.com/api/chat';
+
+ const response = await fetch(API_URL, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ },
+ body: JSON.stringify({
+ message: text,
+ history: chatHistory.slice(-10),
+ codeContext: codeContext,
+ fileContext: fileContextContent,
+ sessionId: currentSessionId
+ })
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ throw new Error("401 Unauthorized: Backend rejected the token. Ensure Render Backend uses the correct Firebase Admin SDK credentials for 'ada-ai-aranag'.");
+ }
+ throw new Error(`API Error: ${response.status}`);
+ }
+
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let fullResponse = "";
+ let aiMsgDiv = document.getElementById(loadingId);
+
+ aiMsgDiv.innerHTML = '<div class="prose text-sm text-neutral-300 max-w-none"></div>';
+ let contentDiv = aiMsgDiv.querySelector('.prose');
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const chunk = decoder.decode(value);
+ fullResponse += chunk;
+
+ const parts = parseResponse(fullResponse);
+ contentDiv.innerHTML = marked.parse(parts.text);
+
+ if (parts.code) {
+ if (editor.getValue().trim() !== parts.code.trim()) {
+ editor.setValue(parts.code);
+ }
+ }
+ chatContainer.scrollTop = chatContainer.scrollHeight;
+ }
+
+ chatHistory.push({ user: text, model: fullResponse });
+ fileContextContent = "";
+ document.getElementById('file-preview').classList.add('hidden');
+
+ } catch (e) {
+ document.getElementById(loadingId).innerText = "Error: " + e.message;
+ }
+ }
+
+ function parseResponse(raw) {
+ const startMarker = "<<<CODE_START>>>";
+ const endMarker = "<<<CODE_END>>>";
+ let text = raw;
+ let code = null;
+ if (raw.includes(startMarker)) {
+ const parts = raw.split(startMarker);
+ text = parts[0];
+ if (parts[1].includes(endMarker)) {
+ code = parts[1].split(endMarker)[0].trim();
+ } else {
+ code = parts[1].trim();
+ }
+ }
+ return { text, code };
+ }
+
+ function appendMessage(role, text) {
+ const div = document.createElement('div');
+ div.className = "flex gap-4 animate-fade-in";
+ if (role === 'user') {
+ div.innerHTML = `
+ <img src="${currentUser.photoURL}" class="w-8 h-8 rounded-full border border-white/10">
+ <div class="flex-1">
+ <div class="text-sm text-neutral-500 font-mono mb-1">You</div>
+ <div class="text-sm text-white bg-neutral-800 p-3 rounded-lg border border-white/5 inline-block">${text}</div>
+ </div>
+ `;
+ }
+ chatContainer.appendChild(div);
+ chatContainer.scrollTop = chatContainer.scrollHeight;
+ }
+
+ function appendLoading() {
+ const id = 'msg-' + Date.now();
+ const div = document.createElement('div');
+ div.className = "flex gap-4";
+ div.innerHTML = `
+ <div class="w-8 h-8 rounded bg-lime-300/10 flex items-center justify-center border border-lime-300/20 text-lime-300 text-xs font-mono">AI</div>
+ <div class="flex-1" id="${id}">
+ <div class="flex gap-1 mt-2">
+ <div class="typing-dot"></div><div class="typing-dot" style="animation-delay:0.2s"></div><div class="typing-dot" style="animation-delay:0.4s"></div>
+ </div>
+ </div>
+ `;
+ chatContainer.appendChild(div);
+ chatContainer.scrollTop = chatContainer.scrollHeight;
+ return id;
+ }
+
+ // --- EDITOR ACTIONS (Exported to Window) ---
+ window.changeLanguage = function(val) {
+ const lang = val || document.getElementById('lang-select').value;
+ editor.setOption("mode", lang);
+ document.getElementById('lang-select').value = lang;
+ }
+
+ window.runCode = function() {
+ const code = editor.getValue();
+ const lang = document.getElementById('lang-select').value;
+ const compressed = LZString.compressToEncodedURIComponent(code);
+ let urlLang = 'python';
+ if (lang === 'clike') urlLang = 'cpp';
+ if (lang === 'javascript') urlLang = 'nodejs';
+ window.open(`https://compiler.amit.is-a.dev/${urlLang}?code=${compressed}`, '_blank');
+ }
+
+ window.switchTab = function(tab) {
+ const left = document.getElementById('left-pane');
+ const right = document.getElementById('right-pane');
+ const tabChat = document.getElementById('tab-chat');
+ const tabCode = document.getElementById('tab-code');
+ if (tab === 'chat') {
+ left.classList.remove('hidden');
+ right.classList.add('hidden');
+ tabChat.classList.add('active', 'text-black', 'border-lime-300');
+ tabChat.classList.remove('text-neutral-500');
+ tabCode.classList.remove('active');
+ tabCode.classList.add('text-neutral-500');
+ } else {
+ left.classList.add('hidden');
+ right.classList.remove('hidden');
+ right.classList.add('flex');
+ editor.refresh();
+ tabCode.classList.add('active');
+ tabCode.classList.remove('text-neutral-500');
+ tabChat.classList.remove('active');
+ tabChat.classList.add('text-neutral-500');
+ }
+ }
+
+ window.toggleSidebar = function() {
+ const sb = document.getElementById('sidebar');
+ if (sb.classList.contains('-translate-x-full')) {
+ sb.classList.remove('-translate-x-full');
+ } else {
+ sb.classList.add('-translate-x-full');
+ }
+ }
+
+ async function loadHistory() {
+ // Placeholder
+ }
+
+ </script>
+</body>
+</html>
+\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
@@ -1,6 +1,6 @@
-Flask
-Flask-Cors
-google-generativeai
-firebase-admin
-python-dotenv
-gunicorn
+Flask>=3.0.0
+Flask-Cors>=4.0.0
+google-generativeai>=0.3.0
+firebase-admin>=6.0.0
+python-dotenv>=1.0.0
+gunicorn>=21.0.0