Shirpi commited on
Commit
1f29ed1
·
verified ·
1 Parent(s): 03c12d0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +331 -0
app.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import time
4
+ import json
5
+ import markdown
6
+ from flask import Flask, request, jsonify, render_template_string
7
+ import google.generativeai as genai # 👈 CLASSIC LIBRARY (STABLE)
8
+
9
+ # ==========================================
10
+ # 👇 API KEYS SETUP 👇
11
+ # ==========================================
12
+ keys_string = os.environ.get("API_KEYS", "")
13
+ API_KEYS = [k.strip() for k in keys_string.replace(',', ' ').replace('\n', ' ').split() if k.strip()]
14
+
15
+ print(f"✅ Loaded {len(API_KEYS)} API Keys.") # Debug Log
16
+
17
+ current_key_index = 0
18
+ app = Flask(__name__)
19
+
20
+ # --- 💾 DATABASE ---
21
+ DB_FILE = "chat_db.json"
22
+
23
+ def load_db():
24
+ try:
25
+ if os.path.exists(DB_FILE):
26
+ with open(DB_FILE, 'r') as f: return json.load(f)
27
+ except: pass
28
+ return {}
29
+
30
+ def save_db(db):
31
+ try:
32
+ with open(DB_FILE, 'w') as f: json.dump(db, f, indent=2)
33
+ except Exception as e:
34
+ print(f"Save Error: {e}")
35
+
36
+ user_db = load_db()
37
+
38
+ # --- 🧠 SYSTEM INSTRUCTION ---
39
+ SYSTEM_INSTRUCTION = """
40
+ ROLE: You are "Student's AI", a coding tutor.
41
+ PERMANENT RULES:
42
+ 1. **BLACK BOX:** Always format code inside Markdown blocks (```python ... ```).
43
+ 2. **INDENTATION:** Code must have perfect spacing.
44
+ 3. **LANGUAGE:** Tamil & English mix (Tanglish).
45
+ 4. **BRIEF:** Keep explanations short and crisp.
46
+ """
47
+
48
+ def generate_with_retry(prompt, history_messages=[]):
49
+ """
50
+ Uses Standard Library (google-generativeai)
51
+ Model: gemini-1.5-flash (Proven & Stable)
52
+ """
53
+ global current_key_index
54
+
55
+ if not API_KEYS:
56
+ return "❌ Error: API Keys Missing in Secrets."
57
+
58
+ # CONVERT HISTORY TO OLD SDK FORMAT
59
+ # Old SDK expects: [{'role': 'user', 'parts': ['text']}, {'role': 'model', 'parts': ['text']}]
60
+ recent_history = history_messages[-10:]
61
+ formatted_history = []
62
+
63
+ for msg in recent_history:
64
+ role = "user" if msg["role"] == "user" else "model"
65
+ formatted_history.append({
66
+ "role": role,
67
+ "parts": [msg["content"]]
68
+ })
69
+
70
+ last_error = ""
71
+
72
+ # RETRY LOOP
73
+ for attempt in range(len(API_KEYS) * 2):
74
+ key = API_KEYS[current_key_index]
75
+
76
+ try:
77
+ # 1. Configure Key
78
+ genai.configure(api_key=key)
79
+
80
+ # 2. Initialize Model (Classic Way)
81
+ model = genai.GenerativeModel(
82
+ model_name="gemini-1.5-flash",
83
+ system_instruction=SYSTEM_INSTRUCTION
84
+ )
85
+
86
+ # 3. Start Chat & Send Message
87
+ chat = model.start_chat(history=formatted_history)
88
+ response = chat.send_message(prompt)
89
+
90
+ return response.text
91
+
92
+ except Exception as e:
93
+ error_msg = str(e)
94
+ print(f"⚠️ Key #{current_key_index+1} Failed: {error_msg}")
95
+
96
+ if "404" in error_msg:
97
+ # If 404 happens here, it's purely a Model Name issue, but gemini-1.5-flash IS correct.
98
+ # So it usually means key access issue.
99
+ pass
100
+
101
+ last_error = error_msg
102
+ current_key_index = (current_key_index + 1) % len(API_KEYS)
103
+ time.sleep(1)
104
+ continue
105
+
106
+ return f"⚠️ **Connection Error:** {last_error}<br>Try restarting the Space."
107
+
108
+ # --- UI TEMPLATE (KEEPING THE WORKING UI) ---
109
+ HTML_TEMPLATE = """
110
+ <!DOCTYPE html>
111
+ <html lang="en">
112
+ <head>
113
+ <meta charset="UTF-8">
114
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
115
+ <title>Student's AI</title>
116
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
117
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
118
+ <style>
119
+ :root { --bg: #000; --sidebar: #0a0a0a; --header: rgba(0,0,0,0.9); --user-bg: #222; --text: #ececec; }
120
+ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
121
+ body, html { margin: 0; padding: 0; height: 100%; width: 100%; background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; overflow: hidden; }
122
+ pre { background: #161616; border: 1px solid #333; border-radius: 8px; padding: 15px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; }
123
+ code { font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #e6edf3; }
124
+ .k { color: #ff7b72; } .s { color: #a5d6ff; } .c { color: #8b949e; } .nf { color: #d2a8ff; } .m { color: #79c0ff; }
125
+ #app-container { display: none; flex-direction: column; height: 100%; }
126
+ 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; }
127
+ #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; }
128
+ #sidebar.open { transform: translateX(0); }
129
+ .overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 90; display: none; }
130
+ .overlay.active { display: block; }
131
+ #chat-box { flex: 1; overflow-y: auto; padding: 20px 20px 40px 20px; display: flex; flex-direction: column; gap: 20px; }
132
+ .msg { display: flex; flex-direction: column; width: 100%; animation: fade 0.3s forwards; }
133
+ @keyframes fade { from{opacity:0; transform:translateY(10px);} to{opacity:1; transform:translateY(0);} }
134
+ .user-msg { align-items: flex-end; }
135
+ .user-msg .bubble { background: var(--user-bg); padding: 12px 18px; border-radius: 18px 18px 4px 18px; max-width: 85%; }
136
+ .ai-msg { align-items: flex-start; }
137
+ .ai-text { width: 100%; line-height: 1.6; font-size: 16px; color: #eee; }
138
+ .ai-text p:first-child { margin-top: 0; }
139
+ .input-wrap { padding: 15px 20px; background: #000; border-top: 1px solid #222; flex-shrink: 0; z-index: 60; }
140
+ .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; }
141
+ 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; }
142
+ .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; }
143
+ .send-btn:active { transform: scale(0.9); background: #ddd; }
144
+ #login-overlay { position: fixed; inset: 0; z-index: 2000; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: center; }
145
+ .login-box { width: 90%; max-width: 400px; padding: 40px; background: #111; border: 1px solid #333; border-radius: 20px; text-align: center; }
146
+ .login-input { width: 100%; padding: 15px; background: #000; border: 1px solid #333; color: #fff; border-radius: 10px; margin-bottom: 20px; text-align: center; }
147
+ .login-btn { width: 100%; padding: 15px; background: #fff; color: #000; border: none; border-radius: 10px; font-weight: 700; cursor: pointer; }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <div id="login-overlay">
152
+ <div class="login-box">
153
+ <h2 style="color:#fff;">Student's AI</h2>
154
+ <input type="text" id="username-input" class="login-input" placeholder="Enter Name" autocomplete="off">
155
+ <button onclick="handleLogin()" class="login-btn">Start</button>
156
+ </div>
157
+ </div>
158
+ <div id="app-container">
159
+ <div class="overlay" id="overlay" onclick="toggleSidebar()"></div>
160
+ <div id="sidebar">
161
+ <div style="padding-bottom:20px; border-bottom:1px solid #222; margin-bottom:20px; font-weight:bold; display:flex; gap:10px; align-items:center;">
162
+ <div id="avatar" style="width:35px; height:35px; background:#333; border-radius:50%; display:flex; align-items:center; justify-content:center;">U</div>
163
+ <span id="display-name">User</span>
164
+ </div>
165
+ <div style="background:#fff; color:#000; padding:12px; border-radius:8px; text-align:center; font-weight:bold; cursor:pointer;" onclick="newChat()">+ New Chat</div>
166
+ <div style="flex:1; overflow-y:auto; margin-top:20px;" id="history-list"></div>
167
+ <div style="margin-top:20px; color:#ff5555; cursor:pointer; text-align:center;" onclick="handleLogout()">Log Out</div>
168
+ </div>
169
+ <header>
170
+ <i class="fas fa-bars" style="color:#fff; font-size:20px; cursor:pointer;" onclick="toggleSidebar()"></i>
171
+ <div style="font-weight:bold; font-size:18px; color:#fff;">Student's AI</div>
172
+ <div style="width:20px;"></div>
173
+ </header>
174
+ <div id="chat-box"></div>
175
+ <div class="input-wrap">
176
+ <div class="input-bar">
177
+ <textarea id="input" placeholder="Type here..." rows="1" oninput="resizeInput(this)"></textarea>
178
+ <button id="sendBtn" class="send-btn" onclick="send(event)"><i class="fas fa-arrow-up"></i></button>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ <script>
183
+ let currentUser = null; let currentChatId = null;
184
+ function resizeInput(el) { el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 100) + 'px'; }
185
+ document.getElementById("input").addEventListener("keydown", function(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(e); }});
186
+ function checkLogin() {
187
+ const stored = localStorage.getItem("student_ai_user");
188
+ if (stored) { currentUser = stored; showApp(); } else { document.getElementById("login-overlay").style.display = "flex"; }
189
+ }
190
+ function handleLogin() {
191
+ const name = document.getElementById("username-input").value.trim();
192
+ if(name) { localStorage.setItem("student_ai_user", name); currentUser = name; showApp(); }
193
+ }
194
+ function handleLogout() { localStorage.removeItem("student_ai_user"); location.reload(); }
195
+ function showApp() {
196
+ document.getElementById("login-overlay").style.display = "none";
197
+ document.getElementById("app-container").style.display = "flex";
198
+ document.getElementById("display-name").innerText = currentUser;
199
+ document.getElementById("avatar").innerText = currentUser[0].toUpperCase();
200
+ loadHistory();
201
+ if(!currentChatId) {
202
+ const box = document.getElementById("chat-box");
203
+ 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>`; }
204
+ }
205
+ }
206
+ const chatBox = document.getElementById('chat-box');
207
+ const input = document.getElementById('input');
208
+ async function send(event) {
209
+ if(event) event.preventDefault();
210
+ const text = input.value.trim();
211
+ if(!text) return;
212
+ input.value = ''; input.style.height = 'auto'; input.focus();
213
+ const userDiv = document.createElement('div'); userDiv.className = 'msg user-msg';
214
+ userDiv.innerHTML = `<div class="bubble">${text}</div>`;
215
+ chatBox.appendChild(userDiv); chatBox.scrollTop = chatBox.scrollHeight;
216
+ const tempId = "ai-" + Date.now();
217
+ const aiDiv = document.createElement('div'); aiDiv.className = 'msg ai-msg'; aiDiv.id = tempId;
218
+ aiDiv.innerHTML = `<div class="ai-text"><i class="fas fa-circle-notch fa-spin"></i> Thinking...</div>`;
219
+ chatBox.appendChild(aiDiv); chatBox.scrollTop = chatBox.scrollHeight;
220
+ try {
221
+ if(!currentChatId) {
222
+ const res = await fetch('/new_chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser }) });
223
+ if(!res.ok) throw new Error("Net Error");
224
+ const data = await res.json();
225
+ currentChatId = data.chat_id;
226
+ }
227
+ const res = await fetch('/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ message: text, username: currentUser, chat_id: currentChatId }) });
228
+ if(!res.ok) throw new Error("API Error");
229
+ const data = await res.json();
230
+ document.getElementById(tempId).remove();
231
+ const finalAiDiv = document.createElement('div'); finalAiDiv.className = 'msg ai-msg';
232
+ finalAiDiv.innerHTML = `<div class="ai-text">${data.response || "Error"}</div>`;
233
+ chatBox.appendChild(finalAiDiv);
234
+ if(data.new_title) loadHistory();
235
+ } catch(e) { document.getElementById(tempId).innerHTML = `<div class="ai-text" style="color:#ff5555">⚠️ Failed: ${e.message}</div>`; }
236
+ chatBox.scrollTop = chatBox.scrollHeight;
237
+ }
238
+ async function loadHistory() {
239
+ try {
240
+ const res = await fetch('/get_history', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser }) });
241
+ const data = await res.json();
242
+ const list = document.getElementById("history-list"); list.innerHTML = "";
243
+ 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>`; });
244
+ } catch(e) {}
245
+ }
246
+ async function newChat(silent=false) {
247
+ try {
248
+ const res = await fetch('/new_chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser }) });
249
+ const data = await res.json(); currentChatId = data.chat_id;
250
+ if(!silent) { chatBox.innerHTML = ''; loadHistory(); toggleSidebar(); }
251
+ } catch(e) {}
252
+ }
253
+ async function loadChat(cid) {
254
+ currentChatId = cid;
255
+ const res = await fetch('/get_chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username: currentUser, chat_id: cid }) });
256
+ const data = await res.json(); chatBox.innerHTML = '';
257
+ data.messages.forEach(msg => {
258
+ const div = document.createElement('div'); div.className = msg.role === 'user' ? 'msg user-msg' : 'msg ai-msg';
259
+ div.innerHTML = msg.role === 'user' ? `<div class="bubble">${msg.content}</div>` : `<div class="ai-text">${msg.content}</div>`;
260
+ chatBox.appendChild(div);
261
+ });
262
+ toggleSidebar(); chatBox.scrollTop = chatBox.scrollHeight;
263
+ }
264
+ function toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); document.getElementById('overlay').classList.toggle('active'); }
265
+ checkLogin();
266
+ </script>
267
+ </body>
268
+ </html>
269
+ """
270
+
271
+ # --- BACKEND ROUTES ---
272
+ @app.route("/", methods=["GET"])
273
+ def home(): return render_template_string(HTML_TEMPLATE)
274
+
275
+ @app.route("/get_history", methods=["POST"])
276
+ def get_history():
277
+ user = request.json.get("username")
278
+ return jsonify({"chats": user_db.get(user, {})})
279
+
280
+ @app.route("/new_chat", methods=["POST"])
281
+ def new_chat():
282
+ user = request.json.get("username")
283
+ if user not in user_db: user_db[user] = {}
284
+ nid = str(uuid.uuid4())
285
+ user_db[user][nid] = {"title": "New Chat", "messages": []}
286
+ save_db(user_db)
287
+ return jsonify({"status": "success", "chat_id": nid})
288
+
289
+ @app.route("/get_chat", methods=["POST"])
290
+ def get_chat():
291
+ data = request.json
292
+ return jsonify({"messages": user_db.get(data.get("username"), {}).get(data.get("chat_id"), {}).get("messages", [])})
293
+
294
+ @app.route("/chat", methods=["POST"])
295
+ def chat():
296
+ data = request.json
297
+ msg = data.get("message")
298
+ user = data.get("username")
299
+ cid = data.get("chat_id")
300
+
301
+ if user not in user_db: user_db[user] = {}
302
+ if cid not in user_db[user]:
303
+ user_db[user][cid] = {"title": "New Chat", "messages": []}
304
+ save_db(user_db)
305
+
306
+ user_db[user][cid]["messages"].append({"role": "user", "content": msg})
307
+ new_title = False
308
+ if len(user_db[user][cid]["messages"]) == 1:
309
+ user_db[user][cid]["title"] = " ".join(msg.split()[:4]) + "..."
310
+ new_title = True
311
+
312
+ save_db(user_db)
313
+
314
+ try:
315
+ previous_messages = user_db[user][cid]['messages'][:-1]
316
+
317
+ # Call the classic generation function
318
+ raw_response = generate_with_retry(msg, previous_messages)
319
+
320
+ formatted_html = markdown.markdown(raw_response, extensions=['fenced_code', 'codehilite'])
321
+
322
+ user_db[user][cid]["messages"].append({"role": "ai", "content": formatted_html})
323
+ save_db(user_db)
324
+
325
+ return jsonify({"response": formatted_html, "new_title": new_title})
326
+
327
+ except Exception as e:
328
+ return jsonify({"error": str(e)})
329
+
330
+ if __name__ == '__main__':
331
+ app.run(host='0.0.0.0', port=7860)