Shirpi commited on
Commit
c55cf25
Β·
verified Β·
1 Parent(s): b877f0b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -239
app.py CHANGED
@@ -7,119 +7,96 @@ from flask import Flask, request, jsonify, render_template_string
7
  import google.generativeai as genai
8
 
9
  # ==========================================
10
- # πŸ‘‡ API KEYS SETUP πŸ‘‡
11
  # ==========================================
12
- keys_string = os.environ.get("API_KEYS", "")
13
- # Clean up keys (remove spaces/newlines)
14
- API_KEYS = [k.strip() for k in keys_string.replace(',', ' ').replace('\n', ' ').split() if k.strip()]
15
 
16
- # πŸ‘‡ MODELS TO TRY (Priority Order) πŸ‘‡
17
- MODELS = ["gemini-1.5-flash", "gemini-1.5-pro", "gemini-pro"]
18
-
19
- print(f"βœ… Loaded {len(API_KEYS)} API Keys.")
20
 
21
  current_key_index = 0
22
  app = Flask(__name__)
23
 
24
  # --- πŸ’Ύ DATABASE ---
25
  DB_FILE = "chat_db.json"
26
-
27
  def load_db():
28
  try:
29
  if os.path.exists(DB_FILE):
30
  with open(DB_FILE, 'r') as f: return json.load(f)
31
  except: pass
32
  return {}
33
-
34
  def save_db(db):
35
  try:
36
  with open(DB_FILE, 'w') as f: json.dump(db, f, indent=2)
37
- except Exception as e:
38
- print(f"Save Error: {e}")
39
-
40
  user_db = load_db()
41
 
42
  # --- 🧠 SYSTEM INSTRUCTION ---
43
  SYSTEM_INSTRUCTION = """
44
  ROLE: You are "Student's AI", a coding tutor.
45
- PERMANENT RULES:
46
- 1. **BLACK BOX:** Always format code inside Markdown blocks (```python ... ```).
47
- 2. **INDENTATION:** Code must have perfect spacing.
48
- 3. **LANGUAGE:** Tamil & English mix (Tanglish).
49
- 4. **BRIEF:** Keep explanations short and crisp.
50
  """
51
 
52
- def get_next_key():
53
- """Rotates to the next available API Key"""
54
- global current_key_index
55
- current_key_index = (current_key_index + 1) % len(API_KEYS)
56
- return API_KEYS[current_key_index]
57
-
58
  def generate_with_retry(prompt, history_messages=[]):
59
- """
60
- SMART LOGIC:
61
- - If 429 (Quota): Switch KEY immediately.
62
- - If 404 (Not Found): Switch MODEL immediately.
63
- """
64
  global current_key_index
65
 
66
- if not API_KEYS: return "❌ Error: API Keys Missing in Secrets."
 
 
67
 
68
- # History Formatting
69
- recent_history = history_messages[-10:]
70
  formatted_history = []
71
- for msg in recent_history:
72
  role = "user" if msg["role"] == "user" else "model"
73
  formatted_history.append({"role": role, "parts": [msg["content"]]})
74
 
75
- last_error = ""
 
 
 
76
 
77
- # We allow (Number of Keys * Number of Models) attempts total
78
- max_attempts = len(API_KEYS) * len(MODELS)
79
 
80
- for attempt in range(max_attempts):
 
81
  key = API_KEYS[current_key_index]
82
 
83
- # Try current key with all models priority
84
- for model_name in MODELS:
85
  try:
86
  genai.configure(api_key=key)
87
- model = genai.GenerativeModel(
88
- model_name=model_name,
89
- system_instruction=SYSTEM_INSTRUCTION
90
- )
91
 
 
92
  chat = model.start_chat(history=formatted_history)
93
  response = chat.send_message(prompt)
 
94
 
95
- # If success, return text
96
- return response.text
97
-
98
  except Exception as e:
99
- error_str = str(e).lower()
 
100
 
101
- # CASE 1: QUOTA EXCEEDED (429) -> SWITCH KEY
102
- if "429" in error_str or "exhausted" in error_str:
103
- print(f"⚠️ Key #{current_key_index+1} Quota Full ({model_name}). Switching Key...")
104
- get_next_key() # Rotate Key
105
- time.sleep(1) # Cool down
106
- break # Break inner model loop, try new key
107
 
108
- # CASE 2: MODEL NOT FOUND (404) -> TRY NEXT MODEL (SAME KEY)
109
- elif "404" in error_str or "not found" in error_str:
110
- print(f"⚠️ {model_name} not found. Trying next model...")
111
- continue # Try next model in the list
112
-
113
- # CASE 3: OTHER ERRORS -> SWITCH KEY
114
- else:
115
- print(f"⚠️ Error on {model_name}: {error_str}")
116
- last_error = error_str
117
- get_next_key()
118
- break
119
 
120
- return f"⚠️ **System Busy:** All keys/models failed. (Last Error: {last_error})"
 
 
 
121
 
122
- # --- UI TEMPLATE (STABLE - NO CHANGES) ---
123
  HTML_TEMPLATE = """
124
  <!DOCTYPE html>
125
  <html lang="en">
@@ -127,219 +104,103 @@ HTML_TEMPLATE = """
127
  <meta charset="UTF-8">
128
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
129
  <title>Student's AI</title>
130
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
131
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
132
  <style>
133
  :root { --bg: #000; --sidebar: #0a0a0a; --header: rgba(0,0,0,0.9); --user-bg: #222; --text: #ececec; }
134
- * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
135
- body, html { margin: 0; padding: 0; height: 100%; width: 100%; background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; overflow: hidden; }
136
- pre { background: #161616; border: 1px solid #333; border-radius: 8px; padding: 15px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; }
137
- code { font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #e6edf3; }
138
- .k { color: #ff7b72; } .s { color: #a5d6ff; } .c { color: #8b949e; } .nf { color: #d2a8ff; } .m { color: #79c0ff; }
139
- #app-container { display: none; flex-direction: column; height: 100%; }
140
- header { height: 60px; padding: 0 20px; background: var(--header); border-bottom: 1px solid #222; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; z-index: 50; }
141
- #sidebar { position: fixed; inset: 0; width: 280px; background: var(--sidebar); border-right: 1px solid #222; transform: translateX(-100%); transition: 0.3s; z-index: 100; padding: 20px; display: flex; flex-direction: column; }
142
- #sidebar.open { transform: translateX(0); }
143
- .overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 90; display: none; }
144
- .overlay.active { display: block; }
145
- #chat-box { flex: 1; overflow-y: auto; padding: 20px 20px 40px 20px; display: flex; flex-direction: column; gap: 20px; }
146
- .msg { display: flex; flex-direction: column; width: 100%; animation: fade 0.3s forwards; }
147
- @keyframes fade { from{opacity:0; transform:translateY(10px);} to{opacity:1; transform:translateY(0);} }
148
- .user-msg { align-items: flex-end; }
149
- .user-msg .bubble { background: var(--user-bg); padding: 12px 18px; border-radius: 18px 18px 4px 18px; max-width: 85%; }
150
- .ai-msg { align-items: flex-start; }
151
- .ai-text { width: 100%; line-height: 1.6; font-size: 16px; color: #eee; }
152
- .ai-text p:first-child { margin-top: 0; }
153
- .input-wrap { padding: 15px 20px; background: #000; border-top: 1px solid #222; flex-shrink: 0; z-index: 60; }
154
- .input-bar { width: 100%; max-width: 800px; margin: 0 auto; background: #161616; border: 1px solid #333; border-radius: 30px; padding: 8px 12px 8px 20px; display: flex; align-items: center; gap: 10px; }
155
- textarea { flex: 1; background: transparent; border: none; color: #fff; font-size: 16px; max-height: 100px; padding: 5px 0; resize: none; outline: none; font-family: 'Inter', sans-serif; }
156
- .send-btn { width: 40px; height: 40px; min-width: 40px; background: #fff; color: #000; border-radius: 50%; border: none; cursor: pointer; display: flex; justify-content: center; align-items: center; transition: 0.2s; z-index: 10; }
157
- .send-btn:active { transform: scale(0.9); background: #ddd; }
158
- #login-overlay { position: fixed; inset: 0; z-index: 2000; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: center; }
159
- .login-box { width: 90%; max-width: 400px; padding: 40px; background: #111; border: 1px solid #333; border-radius: 20px; text-align: center; }
160
- .login-input { width: 100%; padding: 15px; background: #000; border: 1px solid #333; color: #fff; border-radius: 10px; margin-bottom: 20px; text-align: center; }
161
- .login-btn { width: 100%; padding: 15px; background: #fff; color: #000; border: none; border-radius: 10px; font-weight: 700; cursor: pointer; }
162
  </style>
163
  </head>
164
  <body>
165
- <div id="login-overlay">
166
- <div class="login-box">
167
- <h2 style="color:#fff;">Student's AI</h2>
168
- <input type="text" id="username-input" class="login-input" placeholder="Enter Name" autocomplete="off">
169
- <button onclick="handleLogin()" class="login-btn">Start</button>
170
- </div>
171
- </div>
172
  <div id="app-container">
173
- <div class="overlay" id="overlay" onclick="toggleSidebar()"></div>
174
- <div id="sidebar">
175
- <div style="padding-bottom:20px; border-bottom:1px solid #222; margin-bottom:20px; font-weight:bold; display:flex; gap:10px; align-items:center;">
176
- <div id="avatar" style="width:35px; height:35px; background:#333; border-radius:50%; display:flex; align-items:center; justify-content:center;">U</div>
177
- <span id="display-name">User</span>
178
- </div>
179
- <div style="background:#fff; color:#000; padding:12px; border-radius:8px; text-align:center; font-weight:bold; cursor:pointer;" onclick="newChat()">+ New Chat</div>
180
- <div style="flex:1; overflow-y:auto; margin-top:20px;" id="history-list"></div>
181
- <div style="margin-top:20px; color:#ff5555; cursor:pointer; text-align:center;" onclick="handleLogout()">Log Out</div>
182
- </div>
183
- <header>
184
- <i class="fas fa-bars" style="color:#fff; font-size:20px; cursor:pointer;" onclick="toggleSidebar()"></i>
185
- <div style="font-weight:bold; font-size:18px; color:#fff;">Student's AI</div>
186
- <div style="width:20px;"></div>
187
- </header>
188
  <div id="chat-box"></div>
189
  <div class="input-wrap">
190
- <div class="input-bar">
191
- <textarea id="input" placeholder="Type here..." rows="1" oninput="resizeInput(this)"></textarea>
192
- <button id="sendBtn" class="send-btn" onclick="send(event)"><i class="fas fa-arrow-up"></i></button>
193
- </div>
194
  </div>
195
  </div>
196
  <script>
197
- let currentUser = null; let currentChatId = null;
198
- function resizeInput(el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 100) + 'px'; }
199
- document.getElementById("input").addEventListener("keydown", function(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(e); }});
200
- function checkLogin() {
201
- const stored = localStorage.getItem("student_ai_user");
202
- if (stored) { currentUser = stored; showApp(); } else { document.getElementById("login-overlay").style.display = "flex"; }
203
- }
204
- function handleLogin() {
205
- const name = document.getElementById("username-input").value.trim();
206
- if(name) { localStorage.setItem("student_ai_user", name); currentUser = name; showApp(); }
207
- }
208
- function handleLogout() { localStorage.removeItem("student_ai_user"); location.reload(); }
209
- function showApp() {
210
- document.getElementById("login-overlay").style.display = "none";
211
- document.getElementById("app-container").style.display = "flex";
212
- document.getElementById("display-name").innerText = currentUser;
213
- document.getElementById("avatar").innerText = currentUser[0].toUpperCase();
214
- loadHistory();
215
- if(!currentChatId) {
216
- const box = document.getElementById("chat-box");
217
- if(box.innerHTML.trim() === "") { box.innerHTML = `<div class="msg ai-msg"><div class="ai-text"><strong>Welcome ${currentUser}!</strong><br>I am ready.</div></div>`; }
218
- }
219
- }
220
- const chatBox = document.getElementById('chat-box');
221
- const input = document.getElementById('input');
222
- async function send(event) {
223
- if(event) event.preventDefault();
224
  const text = input.value.trim();
225
  if(!text) return;
226
- input.value = ''; input.style.height = 'auto'; input.focus();
227
- const userDiv = document.createElement('div'); userDiv.className = 'msg user-msg';
228
- userDiv.innerHTML = `<div class="bubble">${text}</div>`;
229
- chatBox.appendChild(userDiv); chatBox.scrollTop = chatBox.scrollHeight;
230
- const tempId = "ai-" + Date.now();
231
- const aiDiv = document.createElement('div'); aiDiv.className = 'msg ai-msg'; aiDiv.id = tempId;
232
- aiDiv.innerHTML = `<div class="ai-text"><i class="fas fa-circle-notch fa-spin"></i> Thinking...</div>`;
233
- chatBox.appendChild(aiDiv); chatBox.scrollTop = chatBox.scrollHeight;
 
 
234
  try {
235
  if(!currentChatId) {
236
- const res = await fetch('/new_chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser }) });
237
- if(!res.ok) throw new Error("Net Error");
238
- const data = await res.json();
239
- currentChatId = data.chat_id;
240
  }
241
- const res = await fetch('/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ message: text, username: currentUser, chat_id: currentChatId }) });
242
- if(!res.ok) throw new Error("API Error");
 
 
243
  const data = await res.json();
244
- document.getElementById(tempId).remove();
245
- const finalAiDiv = document.createElement('div'); finalAiDiv.className = 'msg ai-msg';
246
- finalAiDiv.innerHTML = `<div class="ai-text">${data.response || "Error"}</div>`;
247
- chatBox.appendChild(finalAiDiv);
248
- if(data.new_title) loadHistory();
249
- } catch(e) { document.getElementById(tempId).innerHTML = `<div class="ai-text" style="color:#ff5555">⚠️ Failed: ${e.message}</div>`; }
250
  chatBox.scrollTop = chatBox.scrollHeight;
251
  }
252
- async function loadHistory() {
253
- try {
254
- const res = await fetch('/get_history', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser }) });
255
- const data = await res.json();
256
- const list = document.getElementById("history-list"); list.innerHTML = "";
257
- if(data.chats) Object.keys(data.chats).reverse().forEach(cid => { list.innerHTML += `<div style="padding:10px; color:#888; cursor:pointer;" onclick="loadChat('${cid}')"><i class="far fa-comment"></i> ${data.chats[cid].title}</div>`; });
258
- } catch(e) {}
259
- }
260
- async function newChat(silent=false) {
261
- try {
262
- const res = await fetch('/new_chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser }) });
263
- const data = await res.json(); currentChatId = data.chat_id;
264
- if(!silent) { chatBox.innerHTML = ''; loadHistory(); toggleSidebar(); }
265
- } catch(e) {}
266
- }
267
- async function loadChat(cid) {
268
- currentChatId = cid;
269
- const res = await fetch('/get_chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser, chat_id: cid }) });
270
- const data = await res.json(); chatBox.innerHTML = '';
271
- data.messages.forEach(msg => {
272
- const div = document.createElement('div'); div.className = msg.role === 'user' ? 'msg user-msg' : 'msg ai-msg';
273
- div.innerHTML = msg.role === 'user' ? `<div class="bubble">${msg.content}</div>` : `<div class="ai-text">${msg.content}</div>`;
274
- chatBox.appendChild(div);
275
- });
276
- toggleSidebar(); chatBox.scrollTop = chatBox.scrollHeight;
277
- }
278
- function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); document.getElementById('overlay').classList.toggle('active'); }
279
- checkLogin();
280
  </script>
281
  </body>
282
  </html>
283
  """
284
 
285
- # --- BACKEND ROUTES ---
286
  @app.route("/", methods=["GET"])
287
  def home(): return render_template_string(HTML_TEMPLATE)
288
 
289
- @app.route("/get_history", methods=["POST"])
290
- def get_history():
291
- user = request.json.get("username")
292
- return jsonify({"chats": user_db.get(user, {})})
293
-
294
  @app.route("/new_chat", methods=["POST"])
295
  def new_chat():
296
  user = request.json.get("username")
297
  if user not in user_db: user_db[user] = {}
298
  nid = str(uuid.uuid4())
299
- user_db[user][nid] = {"title": "New Chat", "messages": []}
300
  save_db(user_db)
301
- return jsonify({"status": "success", "chat_id": nid})
302
-
303
- @app.route("/get_chat", methods=["POST"])
304
- def get_chat():
305
- data = request.json
306
- return jsonify({"messages": user_db.get(data.get("username"), {}).get(data.get("chat_id"), {}).get("messages", [])})
307
 
308
  @app.route("/chat", methods=["POST"])
309
  def chat():
310
- data = request.json
311
- msg = data.get("message")
312
- user = data.get("username")
313
- cid = data.get("chat_id")
314
 
315
- if user not in user_db: user_db[user] = {}
316
- if cid not in user_db[user]:
317
- user_db[user][cid] = {"title": "New Chat", "messages": []}
318
- save_db(user_db)
319
 
320
- user_db[user][cid]["messages"].append({"role": "user", "content": msg})
321
- new_title = False
322
- if len(user_db[user][cid]["messages"]) == 1:
323
- user_db[user][cid]["title"] = " ".join(msg.split()[:4]) + "..."
324
- new_title = True
325
 
326
- save_db(user_db)
 
 
 
 
327
 
328
- try:
329
- previous_messages = user_db[user][cid]['messages'][:-1]
330
-
331
- # Call the SMART generation function
332
- raw_response = generate_with_retry(msg, previous_messages)
333
-
334
- formatted_html = markdown.markdown(raw_response, extensions=['fenced_code', 'codehilite'])
335
-
336
- user_db[user][cid]["messages"].append({"role": "ai", "content": formatted_html})
337
- save_db(user_db)
338
-
339
- return jsonify({"response": formatted_html, "new_title": new_title})
340
-
341
- except Exception as e:
342
- return jsonify({"error": str(e)})
343
 
344
  if __name__ == '__main__':
345
  app.run(host='0.0.0.0', port=7860)
 
7
  import google.generativeai as genai
8
 
9
  # ==========================================
10
+ # πŸ‘‡ DIAGNOSTIC KEY LOADING πŸ‘‡
11
  # ==========================================
12
+ keys_raw = os.environ.get("API_KEYS", "")
13
+ # Split and clean
14
+ API_KEYS = [k.strip() for k in keys_raw.replace(',', ' ').replace('\n', ' ').split() if k.strip()]
15
 
16
+ # Debug log to console
17
+ print(f"DEBUG: Found {len(API_KEYS)} keys.")
 
 
18
 
19
  current_key_index = 0
20
  app = Flask(__name__)
21
 
22
  # --- πŸ’Ύ DATABASE ---
23
  DB_FILE = "chat_db.json"
 
24
  def load_db():
25
  try:
26
  if os.path.exists(DB_FILE):
27
  with open(DB_FILE, 'r') as f: return json.load(f)
28
  except: pass
29
  return {}
 
30
  def save_db(db):
31
  try:
32
  with open(DB_FILE, 'w') as f: json.dump(db, f, indent=2)
33
+ except: pass
 
 
34
  user_db = load_db()
35
 
36
  # --- 🧠 SYSTEM INSTRUCTION ---
37
  SYSTEM_INSTRUCTION = """
38
  ROLE: You are "Student's AI", a coding tutor.
39
+ RULES: Format code in Markdown (```python). Explain in Tanglish.
 
 
 
 
40
  """
41
 
 
 
 
 
 
 
42
  def generate_with_retry(prompt, history_messages=[]):
 
 
 
 
 
43
  global current_key_index
44
 
45
+ # 1. CHECK KEYS FIRST
46
+ if not API_KEYS:
47
+ return "🚨 **CRITICAL ERROR:** API Keys List is EMPTY. Please check Hugging Face Secrets. Ensure name is `API_KEYS`."
48
 
49
+ # History Format
 
50
  formatted_history = []
51
+ for msg in history_messages[-6:]: # Keep context small to avoid overload
52
  role = "user" if msg["role"] == "user" else "model"
53
  formatted_history.append({"role": role, "parts": [msg["content"]]})
54
 
55
+ # MODELS TO TRY (Standard & Legacy)
56
+ # gemini-1.5-flash: New & Fast
57
+ # gemini-pro: Old & Reliable (Best for free tier)
58
+ models_to_test = ["gemini-1.5-flash", "gemini-pro"]
59
 
60
+ log_messages = [] # To show user if all fails
 
61
 
62
+ # RETRY LOGIC
63
+ for i in range(len(API_KEYS)):
64
  key = API_KEYS[current_key_index]
65
 
66
+ # Try both models for this key
67
+ for model_name in models_to_test:
68
  try:
69
  genai.configure(api_key=key)
70
+ model = genai.GenerativeModel(model_name=model_name, system_instruction=SYSTEM_INSTRUCTION)
 
 
 
71
 
72
+ # Test call
73
  chat = model.start_chat(history=formatted_history)
74
  response = chat.send_message(prompt)
75
+ return response.text # SUCCESS!
76
 
 
 
 
77
  except Exception as e:
78
+ err = str(e)
79
+ log_messages.append(f"Key #{current_key_index+1} | {model_name} -> {err}")
80
 
81
+ # If Quota error, switch key immediately
82
+ if "429" in err or "quota" in err.lower():
83
+ current_key_index = (current_key_index + 1) % len(API_KEYS)
84
+ break # Break model loop, go to next key
 
 
85
 
86
+ # If 404, just try next model
87
+ if "404" in err:
88
+ continue
89
+
90
+ # Move to next key if models failed
91
+ current_key_index = (current_key_index + 1) % len(API_KEYS)
92
+ time.sleep(1)
 
 
 
 
93
 
94
+ # IF WE REACH HERE, EVERYTHING FAILED.
95
+ # PRINT THE LOGS TO THE USER SO WE KNOW WHY.
96
+ error_report = "<br>".join(log_messages)
97
+ return f"⚠️ **ALL FAILED (Debug Report):**<br><small>{error_report}</small>"
98
 
99
+ # --- UI TEMPLATE (Unchanged) ---
100
  HTML_TEMPLATE = """
101
  <!DOCTYPE html>
102
  <html lang="en">
 
104
  <meta charset="UTF-8">
105
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
106
  <title>Student's AI</title>
107
+ <link href="[https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css](https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css)" rel="stylesheet">
 
108
  <style>
109
  :root { --bg: #000; --sidebar: #0a0a0a; --header: rgba(0,0,0,0.9); --user-bg: #222; --text: #ececec; }
110
+ body, html { margin: 0; padding: 0; height: 100%; width: 100%; background: var(--bg); color: var(--text); font-family: sans-serif; overflow: hidden; }
111
+ pre { background: #161616; border: 1px solid #333; padding: 15px; overflow-x: auto; color: #e6edf3; }
112
+ #app-container { display: flex; flex-direction: column; height: 100%; }
113
+ #chat-box { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 20px; }
114
+ .msg { padding: 10px 15px; border-radius: 10px; max-width: 85%; line-height: 1.5; }
115
+ .user-msg { align-self: flex-end; background: #222; }
116
+ .ai-msg { align-self: flex-start; }
117
+ .input-wrap { padding: 15px; background: #000; border-top: 1px solid #333; display: flex; gap: 10px; }
118
+ input { flex: 1; padding: 10px; border-radius: 20px; border: 1px solid #333; background: #111; color: #fff; }
119
+ button { padding: 10px 20px; border-radius: 20px; border: none; background: #fff; cursor: pointer; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  </style>
121
  </head>
122
  <body>
 
 
 
 
 
 
 
123
  <div id="app-container">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  <div id="chat-box"></div>
125
  <div class="input-wrap">
126
+ <input id="input" placeholder="Type here..." onkeydown="if(event.key==='Enter') send()">
127
+ <button onclick="send()">Send</button>
 
 
128
  </div>
129
  </div>
130
  <script>
131
+ let currentChatId = null;
132
+ let currentUser = localStorage.getItem("user") || "User";
133
+
134
+ async function send() {
135
+ const input = document.getElementById('input');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  const text = input.value.trim();
137
  if(!text) return;
138
+
139
+ const chatBox = document.getElementById('chat-box');
140
+ chatBox.innerHTML += `<div class="msg user-msg">${text}</div>`;
141
+ input.value = '';
142
+
143
+ // Loading
144
+ const loadId = "load-"+Date.now();
145
+ chatBox.innerHTML += `<div id="${loadId}" class="msg ai-msg">Thinking...</div>`;
146
+ chatBox.scrollTop = chatBox.scrollHeight;
147
+
148
  try {
149
  if(!currentChatId) {
150
+ const r = await fetch('/new_chat', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({username:currentUser})});
151
+ const d = await r.json(); currentChatId = d.chat_id;
 
 
152
  }
153
+ const res = await fetch('/chat', {
154
+ method: 'POST', headers: {'Content-Type': 'application/json'},
155
+ body: JSON.stringify({message: text, username: currentUser, chat_id: currentChatId})
156
+ });
157
  const data = await res.json();
158
+ document.getElementById(loadId).innerHTML = data.response;
159
+ } catch(e) {
160
+ document.getElementById(loadId).innerHTML = "Error: " + e.message;
161
+ }
 
 
162
  chatBox.scrollTop = chatBox.scrollHeight;
163
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  </script>
165
  </body>
166
  </html>
167
  """
168
 
169
+ # --- ROUTES ---
170
  @app.route("/", methods=["GET"])
171
  def home(): return render_template_string(HTML_TEMPLATE)
172
 
 
 
 
 
 
173
  @app.route("/new_chat", methods=["POST"])
174
  def new_chat():
175
  user = request.json.get("username")
176
  if user not in user_db: user_db[user] = {}
177
  nid = str(uuid.uuid4())
178
+ user_db[user][nid] = {"title": "New", "messages": []}
179
  save_db(user_db)
180
+ return jsonify({"chat_id": nid})
 
 
 
 
 
181
 
182
  @app.route("/chat", methods=["POST"])
183
  def chat():
184
+ d = request.json
185
+ u, cid, msg = d.get("username"), d.get("chat_id"), d.get("message")
 
 
186
 
187
+ if u not in user_db: user_db[u] = {}
188
+ if cid not in user_db[u]: user_db[u][cid] = {"messages": []}
 
 
189
 
190
+ user_db[u][cid]["messages"].append({"role": "user", "content": msg})
191
+
192
+ # CALL DEBUG GENERATOR
193
+ reply = generate_with_retry(msg, user_db[u][cid]["messages"][:-1])
 
194
 
195
+ # Check if it's the specific error report, don't markdown it
196
+ if "DEBUG Report" in reply or "CRITICAL ERROR" in reply:
197
+ formatted_html = reply
198
+ else:
199
+ formatted_html = markdown.markdown(reply, extensions=['fenced_code'])
200
 
201
+ user_db[u][cid]["messages"].append({"role": "model", "content": formatted_html})
202
+ save_db(user_db)
203
+ return jsonify({"response": formatted_html})
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  if __name__ == '__main__':
206
  app.run(host='0.0.0.0', port=7860)