Shirpi commited on
Commit
c5d0bfa
·
verified ·
1 Parent(s): db04039

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -57
app.py CHANGED
@@ -35,8 +35,8 @@ user_db = load_db()
35
  current_key_index = 0
36
  app = Flask(__name__)
37
 
38
- # --- 🧠 SYSTEM INSTRUCTION ---
39
- SYSTEM_INSTRUCTION = """
40
  ROLE: You are "Student's AI", a professional academic tutor.
41
  RULES:
42
  1. **MATH:** Use LaTeX for formulas ($$ ... $$).
@@ -68,7 +68,7 @@ def process_image(image_data):
68
  return Image.open(io.BytesIO(image_bytes))
69
  except: return None
70
 
71
- def generate_with_retry(prompt, image_data=None, file_text=None, history_messages=[]):
72
  global current_key_index
73
  if not API_KEYS: return "🚨 API Keys Missing."
74
 
@@ -78,8 +78,15 @@ def generate_with_retry(prompt, image_data=None, file_text=None, history_message
78
  formatted_history.append({"role": role, "parts": [m["content"]]})
79
 
80
  current_parts = []
 
 
 
 
 
 
81
  if file_text: current_parts.append(f"analyzing file:\n{file_text}\n\n")
82
- current_parts.append(prompt)
 
83
  if image_data:
84
  img = process_image(image_data)
85
  if img: current_parts.append(img)
@@ -94,13 +101,13 @@ def generate_with_retry(prompt, image_data=None, file_text=None, history_message
94
 
95
  try:
96
  genai.configure(api_key=key)
97
- model = genai.GenerativeModel(model_name=model_name, system_instruction=SYSTEM_INSTRUCTION)
98
 
99
  if image_data or file_text:
100
  response = model.generate_content(current_parts)
101
  else:
102
  chat = model.start_chat(history=formatted_history)
103
- response = chat.send_message(prompt)
104
  return response.text
105
  except Exception as e:
106
  current_key_index = (current_key_index + 1) % len(API_KEYS)
@@ -108,7 +115,7 @@ def generate_with_retry(prompt, image_data=None, file_text=None, history_message
108
 
109
  return "⚠️ System Busy. Please try again."
110
 
111
- # --- UI TEMPLATE (Fixed Quotes & Logic) ---
112
  HTML_TEMPLATE = """
113
  <!DOCTYPE html>
114
  <html lang="en">
@@ -149,10 +156,10 @@ HTML_TEMPLATE = """
149
  -webkit-user-select: none; user-select: none;
150
  }
151
 
152
- textarea, input { -webkit-user-select: text !important; user-select: text !important; }
153
  .user-content, .ai-content, code, pre { -webkit-user-select: none !important; user-select: none !important; }
154
 
155
- /* --- APP CONTAINER (Pushed down for Header) --- */
156
  #app-container {
157
  display: flex; flex-direction: column;
158
  height: 100dvh; width: 100%;
@@ -176,6 +183,7 @@ HTML_TEMPLATE = """
176
  }
177
  .menu-btn:active { transform: scale(0.95); background: #222; }
178
  .app-title { font-size: 24px; font-weight: 800; letter-spacing: -0.5px; color: #fff; }
 
179
 
180
  /* --- SIDEBAR ANIMATION --- */
181
  #sidebar {
@@ -234,7 +242,7 @@ HTML_TEMPLATE = """
234
  /* --- LOCKED INTRO TEXT --- */
235
  #intro-container {
236
  position: absolute;
237
- top: 140px; /* Fixed from top so it won't move up with keyboard */
238
  left: 50%;
239
  transform: translateX(-50%);
240
  width: 90%; max-width: 600px;
@@ -278,23 +286,25 @@ HTML_TEMPLATE = """
278
  .preview-img { width: 100%; height: 100%; object-fit: cover; }
279
  .remove-preview { position: absolute; top: -8px; right: -8px; background: red; color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; cursor: pointer; border: none; display: flex; align-items: center; justify-content: center; }
280
 
281
- /* --- LOGIN PAGE LOCKED (FIXED) --- */
282
- #login-overlay {
283
  position: fixed; inset: 0; background: #000; z-index: 2000;
284
- display: flex;
285
- /* Align to START (Top) to prevent centering jump */
286
- align-items: flex-start; justify-content: center;
287
- /* Force padding from top so it never moves */
288
- padding-top: 180px;
289
- transition: opacity 0.8s ease; opacity: 1; pointer-events: auto;
290
- }
291
- #login-overlay.hidden { opacity: 0; pointer-events: none; }
292
 
293
  .login-box {
294
  width: 90%; max-width: 350px; text-align: center;
295
- padding: 40px; border: 1px solid var(--border); border-radius: 20px; background: #0a0a0a;
296
- /* No auto margins */
297
  }
 
 
 
 
 
 
298
 
299
  #image-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 3000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s; }
300
  #image-modal img { max-width: 95%; max-height: 90%; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.8); }
@@ -303,11 +313,32 @@ HTML_TEMPLATE = """
303
  </head>
304
  <body>
305
 
306
- <div id="login-overlay">
307
  <div class="login-box">
308
  <h1 class="app-title" style="margin-bottom:10px;">Student's AI</h1>
309
- <input type="text" id="username-input" placeholder="Your Name" style="width:100%; padding:15px; border-radius:12px; border:1px solid #333; background:#111; color:#fff; text-align:center; outline:none; margin-bottom:20px; font-size: 16px;" onkeydown="if(event.key==='Enter') handleLogin()">
310
- <button onclick="handleLogin()" style="width:100%; padding:15px; border-radius:12px; border:none; background:#fff; font-weight:800; cursor:pointer; font-size: 16px;">Start Learning</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  </div>
312
  </div>
313
 
@@ -333,7 +364,7 @@ HTML_TEMPLATE = """
333
  <header>
334
  <div class="menu-btn" onclick="toggleSidebar()"><i class="fas fa-bars"></i></div>
335
  <span class="app-title">Student's AI</span>
336
- <div style="width:40px;"></div>
337
  </header>
338
 
339
  <div id="chat-box"></div>
@@ -361,22 +392,34 @@ HTML_TEMPLATE = """
361
  <script>
362
  let currentUser = null;
363
  let currentChatId = null;
 
364
  let currentAttachment = { type: null, data: null, name: null };
365
  let longPressTimer;
366
 
367
  // --- FIXED INTRO: LOCKED CONTAINER ---
368
  function getIntroHtml(name) {
369
- return `<div id="intro-container"><div class="msg ai-msg"><div class="ai-content"><h1>Hi ${name},</h1><p>Ready to master your studies today?</p></div></div></div>`;
370
  }
371
 
372
- // --- AUTH LOGIC (SMOOTH TRANSITION) ---
373
  function checkLogin() {
374
  try {
375
- const stored = localStorage.getItem("student_ai_user");
376
- if (stored) {
377
- currentUser = stored;
 
 
378
  document.getElementById("login-overlay").classList.add('hidden');
379
- showApp();
 
 
 
 
 
 
 
 
 
380
  }
381
  } catch(e) { console.log("Storage access denied"); }
382
  }
@@ -387,27 +430,58 @@ HTML_TEMPLATE = """
387
  if(name) {
388
  try { localStorage.setItem("student_ai_user", name); } catch(e){}
389
  currentUser = name;
390
- // FORCE HIDE DIRECTLY
391
- const overlay = document.getElementById("login-overlay");
392
- overlay.classList.add('hidden');
393
- setTimeout(() => overlay.style.display = 'none', 500);
394
- showApp();
395
  } else {
396
  input.style.border = "1px solid red";
397
  setTimeout(() => input.style.border = "1px solid #333", 2000);
398
  }
399
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  function handleLogout() {
401
- try { localStorage.removeItem("student_ai_user"); } catch(e){}
402
- const overlay = document.getElementById("login-overlay");
403
- overlay.style.display = 'flex';
404
- setTimeout(() => overlay.classList.remove('hidden'), 10);
405
- document.getElementById('sidebar').classList.remove('open');
406
- setTimeout(() => {
407
- document.getElementById('chat-box').innerHTML = "";
408
- currentChatId = null;
409
- document.getElementById("username-input").value = "";
410
- }, 500);
411
  }
412
 
413
  function showApp() {
@@ -500,7 +574,14 @@ HTML_TEMPLATE = """
500
  }
501
  const res = await fetch('/chat', {
502
  method: 'POST', headers: {'Content-Type': 'application/json'},
503
- body: JSON.stringify({ message: promptText, image: imgData, file_text: fileText, username: currentUser, chat_id: currentChatId })
 
 
 
 
 
 
 
504
  });
505
  const data = await res.json();
506
 
@@ -655,13 +736,36 @@ HTML_TEMPLATE = """
655
  box.scrollTop = box.scrollHeight;
656
  }
657
 
658
- checkLogin();
659
- </script>
660
- </body>
661
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  """
663
 
664
- # --- BACKEND ROUTES ---
665
  @app.route("/", methods=["GET"])
666
  def home(): return render_template_string(HTML_TEMPLATE)
667
 
@@ -708,12 +812,16 @@ def chat():
708
  u, cid, msg = d.get("username"), d.get("chat_id"), d.get("message")
709
  img_data = d.get("image")
710
  file_text = d.get("file_text")
 
711
 
712
  if u not in user_db: user_db[u] = {}
713
  if cid not in user_db[u]: user_db[u][cid] = {"messages": []}
714
 
715
  user_db[u][cid]["messages"].append({"role": "user", "content": msg})
716
- reply = generate_with_retry(msg, img_data, file_text, user_db[u][cid]["messages"][:-1])
 
 
 
717
  user_db[u][cid]["messages"].append({"role": "model", "content": reply})
718
 
719
  new_title = False
@@ -728,7 +836,7 @@ def chat():
728
  def manifest():
729
  data = {
730
  "name": "Student's AI",
731
- "short_name": "Student's AI",
732
  "start_url": "/",
733
  "display": "standalone",
734
  "orientation": "portrait",
@@ -736,12 +844,12 @@ def manifest():
736
  "theme_color": "#09090b",
737
  "icons": [
738
  {
739
- "src": "https://huggingface.co/spaces/Shirpi/Student-s_AI/resolve/main/1000177401.png",
740
  "sizes": "192x192",
741
  "type": "image/png"
742
  },
743
  {
744
- "src": "https://huggingface.co/spaces/Shirpi/Student-s_AI/resolve/main/1000177401.png",
745
  "sizes": "512x512",
746
  "type": "image/png"
747
  }
 
35
  current_key_index = 0
36
  app = Flask(__name__)
37
 
38
+ # --- 🧠 SYSTEM INSTRUCTION (Dynamic Context) ---
39
+ BASE_INSTRUCTION = """
40
  ROLE: You are "Student's AI", a professional academic tutor.
41
  RULES:
42
  1. **MATH:** Use LaTeX for formulas ($$ ... $$).
 
68
  return Image.open(io.BytesIO(image_bytes))
69
  except: return None
70
 
71
+ def generate_with_retry(prompt, image_data=None, file_text=None, history_messages=[], user_context=""):
72
  global current_key_index
73
  if not API_KEYS: return "🚨 API Keys Missing."
74
 
 
78
  formatted_history.append({"role": role, "parts": [m["content"]]})
79
 
80
  current_parts = []
81
+
82
+ # Add User Context to the prompt (Hidden from user)
83
+ full_prompt = prompt
84
+ if user_context:
85
+ full_prompt = f"[Context: Student is studying {user_context}]\nQuestion: {prompt}"
86
+
87
  if file_text: current_parts.append(f"analyzing file:\n{file_text}\n\n")
88
+ current_parts.append(full_prompt)
89
+
90
  if image_data:
91
  img = process_image(image_data)
92
  if img: current_parts.append(img)
 
101
 
102
  try:
103
  genai.configure(api_key=key)
104
+ model = genai.GenerativeModel(model_name=model_name, system_instruction=BASE_INSTRUCTION)
105
 
106
  if image_data or file_text:
107
  response = model.generate_content(current_parts)
108
  else:
109
  chat = model.start_chat(history=formatted_history)
110
+ response = chat.send_message(full_prompt)
111
  return response.text
112
  except Exception as e:
113
  current_key_index = (current_key_index + 1) % len(API_KEYS)
 
115
 
116
  return "⚠️ System Busy. Please try again."
117
 
118
+ # --- UI TEMPLATE ---
119
  HTML_TEMPLATE = """
120
  <!DOCTYPE html>
121
  <html lang="en">
 
156
  -webkit-user-select: none; user-select: none;
157
  }
158
 
159
+ textarea, input, select { -webkit-user-select: text !important; user-select: text !important; }
160
  .user-content, .ai-content, code, pre { -webkit-user-select: none !important; user-select: none !important; }
161
 
162
+ /* --- APP CONTAINER --- */
163
  #app-container {
164
  display: flex; flex-direction: column;
165
  height: 100dvh; width: 100%;
 
183
  }
184
  .menu-btn:active { transform: scale(0.95); background: #222; }
185
  .app-title { font-size: 24px; font-weight: 800; letter-spacing: -0.5px; color: #fff; }
186
+ .settings-icon { font-size: 18px; color: #aaa; cursor: pointer; }
187
 
188
  /* --- SIDEBAR ANIMATION --- */
189
  #sidebar {
 
242
  /* --- LOCKED INTRO TEXT --- */
243
  #intro-container {
244
  position: absolute;
245
+ top: 140px;
246
  left: 50%;
247
  transform: translateX(-50%);
248
  width: 90%; max-width: 600px;
 
286
  .preview-img { width: 100%; height: 100%; object-fit: cover; }
287
  .remove-preview { position: absolute; top: -8px; right: -8px; background: red; color: white; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; cursor: pointer; border: none; display: flex; align-items: center; justify-content: center; }
288
 
289
+ /* --- LOGIN & SELECTION OVERLAYS (LOCKED & FIXED) --- */
290
+ .overlay {
291
  position: fixed; inset: 0; background: #000; z-index: 2000;
292
+ display: flex; align-items: flex-start; justify-content: center;
293
+ padding-top: 150px;
294
+ transition: opacity 0.5s ease; opacity: 1; pointer-events: auto;
295
+ }
296
+ .overlay.hidden { opacity: 0; pointer-events: none; }
 
 
 
297
 
298
  .login-box {
299
  width: 90%; max-width: 350px; text-align: center;
300
+ padding: 30px; border: 1px solid var(--border); border-radius: 20px; background: #0a0a0a;
 
301
  }
302
+
303
+ /* FORM ELEMENTS */
304
+ .form-label { display: block; text-align: left; font-size: 12px; color: #aaa; margin-bottom: 5px; margin-top: 10px; }
305
+ input, select { width: 100%; padding: 12px; margin-bottom: 5px; background: #18181b; border: 1px solid #333; color: #fff; border-radius: 8px; outline: none; font-family: 'Outfit', sans-serif; font-size: 16px; }
306
+
307
+ .start-btn { width: 100%; padding: 15px; border-radius: 12px; border: none; background: #fff; font-weight: 800; cursor: pointer; font-size: 16px; margin-top: 20px; }
308
 
309
  #image-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 3000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s; }
310
  #image-modal img { max-width: 95%; max-height: 90%; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.8); }
 
313
  </head>
314
  <body>
315
 
316
+ <div id="login-overlay" class="overlay">
317
  <div class="login-box">
318
  <h1 class="app-title" style="margin-bottom:10px;">Student's AI</h1>
319
+ <input type="text" id="username-input" placeholder="Your Name" onkeydown="if(event.key==='Enter') handleLogin()">
320
+ <button class="start-btn" onclick="handleLogin()">Next</button>
321
+ </div>
322
+ </div>
323
+
324
+ <div id="selection-overlay" class="overlay hidden">
325
+ <div class="login-box">
326
+ <h2 style="color:#fff; margin-top:0;">Customize Profile</h2>
327
+
328
+ <span class="form-label">Education Level</span>
329
+ <select id="edu-level" onchange="updateEduOptions()">
330
+ <option value="school">School (6th - 12th)</option>
331
+ <option value="college">College (Arts/Engg)</option>
332
+ </select>
333
+
334
+ <span class="form-label">Class / Year</span>
335
+ <select id="edu-year">
336
+ </select>
337
+
338
+ <span class="form-label">Subject (Optional)</span>
339
+ <input type="text" id="edu-subject" placeholder="Ex: Maths, Physics...">
340
+
341
+ <button class="start-btn" onclick="handleSelection()">Start Learning</button>
342
  </div>
343
  </div>
344
 
 
364
  <header>
365
  <div class="menu-btn" onclick="toggleSidebar()"><i class="fas fa-bars"></i></div>
366
  <span class="app-title">Student's AI</span>
367
+ <div class="settings-icon" onclick="openSettings()"><i class="fas fa-cog"></i></div>
368
  </header>
369
 
370
  <div id="chat-box"></div>
 
392
  <script>
393
  let currentUser = null;
394
  let currentChatId = null;
395
+ let userContext = "";
396
  let currentAttachment = { type: null, data: null, name: null };
397
  let longPressTimer;
398
 
399
  // --- FIXED INTRO: LOCKED CONTAINER ---
400
  function getIntroHtml(name) {
401
+ return `<div id="intro-container"><div class="msg ai-msg"><div class="ai-content"><h1>Hi ${name},</h1><p>Ready to master ${userContext ? userContext.split(',')[1] : "studies"}?</p></div></div></div>`;
402
  }
403
 
404
+ // --- AUTH & ONBOARDING LOGIC ---
405
  function checkLogin() {
406
  try {
407
+ const storedUser = localStorage.getItem("student_ai_user");
408
+ const storedContext = localStorage.getItem("student_ai_context");
409
+
410
+ if (storedUser) {
411
+ currentUser = storedUser;
412
  document.getElementById("login-overlay").classList.add('hidden');
413
+
414
+ if (storedContext) {
415
+ userContext = storedContext;
416
+ document.getElementById("selection-overlay").classList.add('hidden');
417
+ showApp();
418
+ } else {
419
+ // User exists but context missing (New Feature)
420
+ document.getElementById("selection-overlay").classList.remove('hidden');
421
+ updateEduOptions();
422
+ }
423
  }
424
  } catch(e) { console.log("Storage access denied"); }
425
  }
 
430
  if(name) {
431
  try { localStorage.setItem("student_ai_user", name); } catch(e){}
432
  currentUser = name;
433
+ // Hide Login -> Show Selection
434
+ document.getElementById("login-overlay").classList.add('hidden');
435
+ document.getElementById("selection-overlay").classList.remove('hidden');
436
+ updateEduOptions();
 
437
  } else {
438
  input.style.border = "1px solid red";
439
  setTimeout(() => input.style.border = "1px solid #333", 2000);
440
  }
441
  }
442
+
443
+ function updateEduOptions() {
444
+ const level = document.getElementById('edu-level').value;
445
+ const yearSelect = document.getElementById('edu-year');
446
+ yearSelect.innerHTML = "";
447
+
448
+ let options = [];
449
+ if (level === 'school') {
450
+ options = ["6th Std", "7th Std", "8th Std", "9th Std", "10th Std", "11th Std", "12th Std"];
451
+ } else {
452
+ options = ["1st Year", "2nd Year", "3rd Year", "4th Year"];
453
+ }
454
+
455
+ options.forEach(opt => {
456
+ const el = document.createElement("option");
457
+ el.value = opt; el.innerText = opt;
458
+ yearSelect.appendChild(el);
459
+ });
460
+ }
461
+
462
+ function handleSelection() {
463
+ const level = document.getElementById('edu-level').value;
464
+ const year = document.getElementById('edu-year').value;
465
+ const subject = document.getElementById('edu-subject').value;
466
+
467
+ // Save Context
468
+ userContext = `${level}, ${year}, ${subject}`;
469
+ try { localStorage.setItem("student_ai_context", userContext); } catch(e){}
470
+
471
+ document.getElementById("selection-overlay").classList.add('hidden');
472
+ showApp();
473
+ }
474
+
475
+ function openSettings() {
476
+ document.getElementById("selection-overlay").classList.remove('hidden');
477
+ }
478
+
479
  function handleLogout() {
480
+ try {
481
+ localStorage.removeItem("student_ai_user");
482
+ localStorage.removeItem("student_ai_context");
483
+ } catch(e){}
484
+ location.reload(); // Simple reload to reset
 
 
 
 
 
485
  }
486
 
487
  function showApp() {
 
574
  }
575
  const res = await fetch('/chat', {
576
  method: 'POST', headers: {'Content-Type': 'application/json'},
577
+ body: JSON.stringify({
578
+ message: promptText,
579
+ image: imgData,
580
+ file_text: fileText,
581
+ username: currentUser,
582
+ chat_id: currentChatId,
583
+ user_context: userContext // SENDING CONTEXT TO BACKEND
584
+ })
585
  });
586
  const data = await res.json();
587
 
 
736
  box.scrollTop = box.scrollHeight;
737
  }
738
 
739
+ @app.route('/manifest.json')
740
+ def manifest():
741
+ data = {
742
+ "name": "Student's AI",
743
+ "short_name": "StudentAI",
744
+ "start_url": "/",
745
+ "display": "standalone",
746
+ "orientation": "portrait",
747
+ "background_color": "#09090b",
748
+ "theme_color": "#09090b",
749
+ "icons": [
750
+ {
751
+ "src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
752
+ "sizes": "192x192",
753
+ "type": "image/png"
754
+ },
755
+ {
756
+ "src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
757
+ "sizes": "512x512",
758
+ "type": "image/png"
759
+ }
760
+ ]
761
+ }
762
+ return Response(json.dumps(data), mimetype='application/json')
763
+
764
+ if __name__ == '__main__':
765
+ app.run(host='0.0.0.0', port=7860)
766
  """
767
 
768
+ # --- BACKEND ROUTES START HERE ---
769
  @app.route("/", methods=["GET"])
770
  def home(): return render_template_string(HTML_TEMPLATE)
771
 
 
812
  u, cid, msg = d.get("username"), d.get("chat_id"), d.get("message")
813
  img_data = d.get("image")
814
  file_text = d.get("file_text")
815
+ user_context = d.get("user_context", "") # Capture User Context
816
 
817
  if u not in user_db: user_db[u] = {}
818
  if cid not in user_db[u]: user_db[u][cid] = {"messages": []}
819
 
820
  user_db[u][cid]["messages"].append({"role": "user", "content": msg})
821
+
822
+ # PASS USER CONTEXT TO GENERATION FUNCTION
823
+ reply = generate_with_retry(msg, img_data, file_text, user_db[u][cid]["messages"][:-1], user_context)
824
+
825
  user_db[u][cid]["messages"].append({"role": "model", "content": reply})
826
 
827
  new_title = False
 
836
  def manifest():
837
  data = {
838
  "name": "Student's AI",
839
+ "short_name": "StudentAI",
840
  "start_url": "/",
841
  "display": "standalone",
842
  "orientation": "portrait",
 
844
  "theme_color": "#09090b",
845
  "icons": [
846
  {
847
+ "src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
848
  "sizes": "192x192",
849
  "type": "image/png"
850
  },
851
  {
852
+ "src": "https://cdn-icons-png.flaticon.com/512/4712/4712035.png",
853
  "sizes": "512x512",
854
  "type": "image/png"
855
  }