Sazid2 commited on
Commit
29a8221
·
verified ·
1 Parent(s): eb88ed2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -149
app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  Jajabor – SEBA Assamese Class 10 Tutor (Free-tier CPU-ready)
3
- Fixed version with correct Gradio version and improved error handling
4
  """
5
 
6
  import os
@@ -32,9 +32,9 @@ USE_HF_INFERENCE = False
32
  LLM_LOCAL_NAME = "google/flan-t5-small"
33
  LLM_MAX_TOKENS = 128
34
 
35
- CHUNK_SIZE = 600
36
- CHUNK_OVERLAP = 120
37
- TOP_K = 5
38
 
39
  # -------------------- DATABASE --------------------
40
  def init_db(path=DB_PATH):
@@ -139,8 +139,9 @@ def load_all_pdfs(pdf_dir: str):
139
  path = os.path.join(pdf_dir, fname)
140
  print("Reading:", path)
141
  text = extract_text_from_pdf(path)
142
- texts.append(text)
143
- metas.append({"source": fname})
 
144
  return texts, metas
145
 
146
  def split_text(text: str, chunk_size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
@@ -164,7 +165,7 @@ embedding_model = SentenceTransformer(EMBEDDING_MODEL_NAME)
164
 
165
  print("Loading PDFs from", PDF_DIR)
166
  all_texts, all_metas = load_all_pdfs(PDF_DIR)
167
- print("Number of PDFs:", len(all_texts))
168
 
169
  corpus_chunks = []
170
  corpus_metas = []
@@ -178,7 +179,7 @@ index = None
178
  if len(corpus_chunks) > 0:
179
  print("Encoding chunks...")
180
  try:
181
- embs = embedding_model.encode(corpus_chunks, batch_size=32, show_progress_bar=False).astype("float32")
182
  dim = embs.shape[1]
183
  index = faiss.IndexFlatL2(dim)
184
  index.add(embs)
@@ -217,49 +218,54 @@ llm_pipe = None
217
  try:
218
  tokenizer = AutoTokenizer.from_pretrained(LLM_LOCAL_NAME)
219
  model = AutoModelForSeq2SeqLM.from_pretrained(LLM_LOCAL_NAME)
220
- llm_pipe = pipeline("text2text-generation", model=model, tokenizer=tokenizer, device=-1) # CPU
221
- print("Local LLM loaded.")
 
 
 
 
 
 
222
  except Exception as e:
223
  print("Failed to load local LLM:", e)
224
  llm_pipe = None
225
 
226
- SYSTEM_PROMPT = """
227
- You are "Jajabor", an expert SEBA Assamese tutor for Class 10.
228
- Always prefer to answer in Assamese. If the student clearly asks for English, you may reply in English.
229
-
230
- Rules:
231
- - Use ONLY the given textbook context.
232
- - If you are not sure, say: "এই প্ৰশ্নটো পাঠ্যপুথিৰ অংশত স্পষ্টকৈ নাই, সেয়েহে মই নিশ্চিত নহয়।"
233
- - বোঝাপৰা সহজ ভাষাত ব্যাখ্যা কৰা, উদাহৰণ দিয়ক।
234
- - If it is a maths question, explain step-by-step clearly.
235
- """
236
 
237
  def build_rag_prompt(context_blocks, question, chat_history):
238
  ctx = ""
239
  for i, block in enumerate(context_blocks, start=1):
240
  src = block["meta"].get("source", "textbook")
241
- ctx += f"\n[Context {i} {src}]\n{block['text']}\n"
242
 
243
  hist = ""
244
- for role, msg in chat_history[-4:]: # Keep last 4 exchanges
245
- hist += f"{role}: {msg}\n"
 
 
 
246
 
247
  prompt = f"""{SYSTEM_PROMPT}
248
 
249
- পূৰ্বৰ বাৰ্তাসমূহ:
250
  {hist}
251
 
252
- সদস্যৰ প্ৰশ্ন:
253
  {question}
254
 
255
- সম্পৰ্কিত পাঠ্যপুথিৰ অংশ:
256
  {ctx}
257
 
258
- এতিয়া একেদম সহায়ক আৰু বুজিবলৈ সহজ উত্তৰ দিয়া।
259
- """
260
  return prompt
261
 
262
  def llm_answer_with_rag(question: str, chat_history):
 
 
 
263
  retrieved = rag_search(question, TOP_K)
264
  if not retrieved:
265
  return "মই এই প্ৰশ্নৰ উত্তৰ দিবলৈ প্ৰয়োজনীয় তথ্য বিচাৰি পোৱা নাই। দয়া কৰি নিশ্চিত কৰক যে আপোনাৰ পাঠ্যপুথিৰ PDF ফাইলসমূহ সঠিকভাৱে আপলোড কৰা হৈছে।"
@@ -270,15 +276,23 @@ def llm_answer_with_rag(question: str, chat_history):
270
  return "AI মডেল ল'ড হোৱা নাই। দয়া কৰি পুনৰ চেষ্টা কৰক।"
271
 
272
  try:
273
- out = llm_pipe(prompt, max_new_tokens=LLM_MAX_TOKENS, do_sample=False)
 
 
 
 
 
274
  if isinstance(out, list) and len(out) > 0:
275
- if "generated_text" in out[0]:
276
  return out[0]["generated_text"]
277
- return str(out[0])
278
- return str(out)
 
 
 
279
  except Exception as e:
280
  print("LLM generation error:", e)
281
- return f"উত্তৰ তৈয়াৰ কৰোঁতে সমস্যা: {e}"
282
 
283
  # -------------------- OCR + Math helpers --------------------
284
  def ocr_from_image(img_path: str):
@@ -287,7 +301,7 @@ def ocr_from_image(img_path: str):
287
  try:
288
  img = Image.open(img_path)
289
  img = img.convert("RGB")
290
- text = pytesseract.image_to_string(img, lang="eng+asm")
291
  return text.strip()
292
  except Exception as e:
293
  print("OCR error:", e)
@@ -297,41 +311,52 @@ def is_likely_math(text: str) -> bool:
297
  if not text:
298
  return False
299
  math_chars = set("0123456789+-*/=^()%")
300
- if any(ch in text for ch in math_chars):
 
301
  return True
302
- math_kws = ["গণিত", "সমীকৰণ", "উদাহৰণ", "প্ৰশ্ন", "বীজগণিত", "solve", "equation", "math"]
303
  return any(k in text.lower() for k in math_kws)
304
 
305
  def solve_math_expression(expr: str):
306
  try:
307
- expr = expr.replace("^", "**")
308
- if "=" in expr:
309
- left, right = expr.split("=", 1)
310
- left_s = sp.sympify(left.strip())
311
- right_s = sp.sympify(right.strip())
312
- eq = sp.Eq(left_s, right_s)
313
- sol = sp.solve(eq)
314
- explanation = f"সমীকৰণ: {eq}\n\nসমাধান: {sol}"
 
 
 
 
 
 
 
 
 
 
 
315
  else:
316
- expr_s = sp.sympify(expr)
317
- simp = sp.simplify(expr_s)
318
- explanation = f"প্ৰকাশ: {expr}\n\nসৰলীকৃত: {simp}"
319
- return explanation
 
320
  except Exception as e:
321
- return f"গণিত সমাধানত সমস্যা: {e}"
322
-
323
- def speech_to_text(audio):
324
- return "" # Stub for future implementation
325
-
326
- def text_to_speech(text: str):
327
- return None # Stub for future implementation
328
 
329
  # -------------------- Chat logic --------------------
330
- def login_user(username, user_state):
331
  username = (username or "").strip()
332
  if not username:
333
- return user_state, "⚠️ অনুগ্ৰহ কৰি প্ৰথমে লগিনৰ বাবে এটা নাম লিখক।"
 
334
  user_id = get_or_create_user(username)
 
 
 
335
  user_state = {"username": username, "user_id": user_id}
336
  total, math_count = get_user_stats(user_id)
337
  stats = (
@@ -345,142 +370,169 @@ def chat_logic(text_input, image_input, chat_history, user_state):
345
  if chat_history is None:
346
  chat_history = []
347
 
 
348
  if not user_state or not user_state.get("user_id"):
349
- sys_msg = "⚠️ প্ৰথমে ওপৰত আপোনাৰ নাম লিখি **Login / লগিন** টিপক।"
350
- chat_history.append([text_input or "", sys_msg])
351
- return chat_history, user_state, None
352
 
353
  user_id = user_state["user_id"]
354
  final_query_parts = []
355
 
356
  # Process image OCR
357
- ocr_text = ""
358
  if image_input is not None:
359
  ocr_text = ocr_from_image(image_input)
360
  if ocr_text:
361
- final_query_parts.append(f"ছবিৰ পৰা পাঠ: {ocr_text}")
362
 
363
- if text_input:
364
- final_query_parts.append(text_input)
365
 
366
  if not final_query_parts:
367
- sys_msg = "⚠️ অনুগ্ৰহ কৰি প্ৰশ্ন লিখক, কিম্বা ছবি আপলোড কৰক।"
368
- chat_history.append(["", sys_msg])
369
- return chat_history, user_state, None
370
 
371
  full_query = "\n".join(final_query_parts)
372
 
373
- # Convert chat history to conversation format
374
- conv = []
375
- for u, b in chat_history:
376
- if u and u.strip():
377
- conv.append(("Student", u.strip()))
378
- if b and b.strip():
379
- conv.append(("Tutor", b.strip()))
380
-
381
  is_math = is_likely_math(full_query)
382
 
383
  if is_math:
384
  math_answer = solve_math_expression(full_query)
385
- combined_question = (
386
- full_query + "\n\nগণিত সমাধান:\n" + math_answer +
387
- "\n\nঅনুগ্ৰহ কৰি শ্রেণী ১০ ৰ শিক্ষাৰ্থীৰ বাবে সহজ ভাষাত ব্যাখ্যা কৰক।"
388
- )
389
- final_answer = llm_answer_with_rag(combined_question, conv)
390
  else:
391
- final_answer = llm_answer_with_rag(full_query, conv)
392
 
393
  log_interaction(user_id, full_query, final_answer, is_math)
394
 
395
- display_question = text_input or ocr_text or "(ছবিৰ প্ৰশ্ন)"
396
  chat_history.append([display_question, final_answer])
397
 
398
- return chat_history, user_state, None
399
 
400
- # -------------------- Gradio UI --------------------
401
- with gr.Blocks(title=APP_NAME, css="""
402
- .stats-box { background: #f0f8ff; padding: 10px; border-radius: 5px; }
403
- """) as demo:
404
- gr.Markdown(
405
- f"""
406
- # 🧭 {APP_NAME}
407
-
408
- - SEBA Class 10 PDFs upload to `pdfs/class10` folder
409
- - Text + Image (OCR) input support
410
- - Math step-by-step solutions
411
- - User login + progress tracking
412
- """
413
- )
414
-
415
- user_state = gr.State({})
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  with gr.Row():
418
  with gr.Column(scale=1):
419
- gr.Markdown("### 👤 লগিন")
420
- username_inp = gr.Textbox(
421
- label="নাম / ইউজাৰ আইডি",
422
- placeholder="উদাহৰণ: abu10, student01 ..."
423
- )
424
- login_btn = gr.Button("✅ Login / লগিন")
425
- stats_md = gr.Markdown("এতিয়ালৈকে লগিন হোৱা নাই।", elem_classes="stats-box")
426
-
427
- gr.Markdown(
428
- """
429
- ### 💡 টিপছ
430
- - "ক্লাছ ১০ গণিত: উদাহৰণ ৩.১ প্ৰশ্ন ২" – এই ধৰণৰ প্ৰশ্ন ভাল
431
- - ফটো আপলোড কৰিলে টেক্স্টটো OCR কৰি পঢ়িব চেষ্টা কৰা হয়
432
- - সম্ভৱ হলে প্ৰশ্নটো অসমীয়াত সোধক 🙂
433
- """
434
- )
435
 
436
  with gr.Column(scale=3):
437
- chat = gr.Chatbot(label="জাজাবৰ সৈতে কথোপকথন", height=500)
438
- text_inp = gr.Textbox(
439
- label="আপোনাৰ প্ৰশ্ন লিখক",
440
- placeholder='উদাহৰণ: "ক্লাছ ১০ অসমীয়া: অনুচ্ছেদ পাঠ ১ ৰ মূল বিষয় কি?"',
441
- lines=2,
442
  )
443
-
 
 
 
 
 
 
 
 
444
  with gr.Row():
445
- image_inp = gr.Image(label="📷 প্ৰশ্নৰ ছবি (Optional)", type="filepath")
 
 
 
 
446
 
447
  with gr.Row():
448
- ask_btn = gr.Button("🤖 জাজাবৰক সোধক")
449
- clear_btn = gr.Button("🧹 পৰিষ্কাৰ কৰক")
450
 
451
- # Login handler
452
  login_btn.click(
453
  login_user,
454
- inputs=[username_inp, user_state],
455
- outputs=[user_state, stats_md],
456
  )
457
-
458
- # Chat handler
 
 
 
459
  ask_btn.click(
460
- chat_logic,
461
- inputs=[text_inp, image_inp, chat, user_state],
462
- outputs=[chat, user_state, image_inp],
463
- ).then(
464
- lambda: "", None, text_inp
465
  ).then(
466
- lambda: None, None, image_inp
 
467
  )
468
-
469
- # Text submit handler
470
  text_inp.submit(
471
- chat_logic,
472
- inputs=[text_inp, image_inp, chat, user_state],
473
- outputs=[chat, user_state, image_inp],
474
- ).then(
475
- lambda: "", None, text_inp
476
  ).then(
477
- lambda: None, None, image_inp
 
 
 
 
 
 
478
  )
479
-
480
- # Clear chat
481
- def clear_chat():
482
- return [], None
483
- clear_btn.click(clear_chat, outputs=[chat, image_inp])
484
 
485
  if __name__ == "__main__":
486
- demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
  Jajabor – SEBA Assamese Class 10 Tutor (Free-tier CPU-ready)
3
+ Fixed version with Gradio compatibility fixes
4
  """
5
 
6
  import os
 
32
  LLM_LOCAL_NAME = "google/flan-t5-small"
33
  LLM_MAX_TOKENS = 128
34
 
35
+ CHUNK_SIZE = 400 # Reduced for better performance
36
+ CHUNK_OVERLAP = 80
37
+ TOP_K = 3 # Reduced for faster retrieval
38
 
39
  # -------------------- DATABASE --------------------
40
  def init_db(path=DB_PATH):
 
139
  path = os.path.join(pdf_dir, fname)
140
  print("Reading:", path)
141
  text = extract_text_from_pdf(path)
142
+ if text.strip():
143
+ texts.append(text)
144
+ metas.append({"source": fname})
145
  return texts, metas
146
 
147
  def split_text(text: str, chunk_size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
 
165
 
166
  print("Loading PDFs from", PDF_DIR)
167
  all_texts, all_metas = load_all_pdfs(PDF_DIR)
168
+ print("Number of PDFs with content:", len(all_texts))
169
 
170
  corpus_chunks = []
171
  corpus_metas = []
 
179
  if len(corpus_chunks) > 0:
180
  print("Encoding chunks...")
181
  try:
182
+ embs = embedding_model.encode(corpus_chunks, batch_size=16, show_progress_bar=False).astype("float32")
183
  dim = embs.shape[1]
184
  index = faiss.IndexFlatL2(dim)
185
  index.add(embs)
 
218
  try:
219
  tokenizer = AutoTokenizer.from_pretrained(LLM_LOCAL_NAME)
220
  model = AutoModelForSeq2SeqLM.from_pretrained(LLM_LOCAL_NAME)
221
+ llm_pipe = pipeline(
222
+ "text2text-generation",
223
+ model=model,
224
+ tokenizer=tokenizer,
225
+ device=-1, # CPU
226
+ torch_dtype="auto"
227
+ )
228
+ print("✅ Local LLM loaded successfully")
229
  except Exception as e:
230
  print("Failed to load local LLM:", e)
231
  llm_pipe = None
232
 
233
+ SYSTEM_PROMPT = """You are "Jajabor", an expert SEBA Assamese tutor for Class 10.
234
+ Answer in Assamese unless the student asks for English.
235
+ Use the textbook context provided. If unsure, say you don't know.
236
+ Explain simply with examples."""
 
 
 
 
 
 
237
 
238
  def build_rag_prompt(context_blocks, question, chat_history):
239
  ctx = ""
240
  for i, block in enumerate(context_blocks, start=1):
241
  src = block["meta"].get("source", "textbook")
242
+ ctx += f"[Context {i} - {src}]\n{block['text']}\n\n"
243
 
244
  hist = ""
245
+ for u, a in chat_history[-3:]: # Last 3 exchanges
246
+ if u:
247
+ hist += f"Student: {u}\n"
248
+ if a:
249
+ hist += f"Tutor: {a}\n"
250
 
251
  prompt = f"""{SYSTEM_PROMPT}
252
 
253
+ Previous conversation:
254
  {hist}
255
 
256
+ Student's question:
257
  {question}
258
 
259
+ Textbook content:
260
  {ctx}
261
 
262
+ Provide a helpful, easy-to-understand answer in Assamese:"""
 
263
  return prompt
264
 
265
  def llm_answer_with_rag(question: str, chat_history):
266
+ if not question.strip():
267
+ return "অনুগ্ৰহ কৰি এটা প্ৰশ্ন সোধক।"
268
+
269
  retrieved = rag_search(question, TOP_K)
270
  if not retrieved:
271
  return "মই এই প্ৰশ্নৰ উত্তৰ দিবলৈ প্ৰয়োজনীয় তথ্য বিচাৰি পোৱা নাই। দয়া কৰি নিশ্চিত কৰক যে আপোনাৰ পাঠ্যপুথিৰ PDF ফাইলসমূহ সঠিকভাৱে আপলোড কৰা হৈছে।"
 
276
  return "AI মডেল ল'ড হোৱা নাই। দয়া কৰি পুনৰ চেষ্টা কৰক।"
277
 
278
  try:
279
+ out = llm_pipe(
280
+ prompt,
281
+ max_new_tokens=LLM_MAX_TOKENS,
282
+ do_sample=False,
283
+ temperature=0.3
284
+ )
285
  if isinstance(out, list) and len(out) > 0:
286
+ if hasattr(out[0], 'get') and "generated_text" in out[0]:
287
  return out[0]["generated_text"]
288
+ elif isinstance(out[0], str):
289
+ return out[0]
290
+ else:
291
+ return str(out[0])
292
+ return "উত্তৰ তৈয়াৰ কৰোঁতে সমস্যা হ'ল।"
293
  except Exception as e:
294
  print("LLM generation error:", e)
295
+ return f"উত্তৰ তৈয়াৰ কৰোঁতে ত্ৰুটি: {str(e)}"
296
 
297
  # -------------------- OCR + Math helpers --------------------
298
  def ocr_from_image(img_path: str):
 
301
  try:
302
  img = Image.open(img_path)
303
  img = img.convert("RGB")
304
+ text = pytesseract.image_to_string(img, lang="eng")
305
  return text.strip()
306
  except Exception as e:
307
  print("OCR error:", e)
 
311
  if not text:
312
  return False
313
  math_chars = set("0123456789+-*/=^()%")
314
+ text_chars = set(text)
315
+ if math_chars.intersection(text_chars):
316
  return True
317
+ math_kws = ["গণিত", "সমীকৰণ", "উদাহৰণ", "প্ৰশ্ন", "বীজগণিত", "solve", "equation", "math", "calculate"]
318
  return any(k in text.lower() for k in math_kws)
319
 
320
  def solve_math_expression(expr: str):
321
  try:
322
+ # Clean the expression
323
+ expr = expr.strip()
324
+ expr = expr.replace('^', '**')
325
+
326
+ if '=' in expr:
327
+ parts = expr.split('=')
328
+ if len(parts) == 2:
329
+ left = sp.sympify(parts[0].strip())
330
+ right = sp.sympify(parts[1].strip())
331
+ equation = sp.Eq(left, right)
332
+ solutions = sp.solve(equation)
333
+
334
+ if solutions:
335
+ solution_str = f"সমীকৰণ: {equation}\n\nসমাধান: x = {solutions[0]}"
336
+ if len(solutions) > 1:
337
+ solution_str += f"\nবা x = {solutions[1]}"
338
+ return solution_str
339
+ else:
340
+ return "কোনো সমাধান পোৱা নগ'ল।"
341
  else:
342
+ # Just simplify the expression
343
+ expr_sym = sp.sympify(expr)
344
+ simplified = sp.simplify(expr_sym)
345
+ return f"প্ৰকাশ: {expr}\n\nসৰলীকৃত: {simplified}"
346
+
347
  except Exception as e:
348
+ return f"গণিত সমাধানত সমস্যা: {str(e)}\nদয়া কৰি স্পষ্টকৈ লিখক, যেনে: 2*x + 3 = 7"
 
 
 
 
 
 
349
 
350
  # -------------------- Chat logic --------------------
351
+ def login_user(username):
352
  username = (username or "").strip()
353
  if not username:
354
+ return {}, "⚠️ অনুগ্ৰহ কৰি প্ৰথমে লগিনৰ বাবে এটা নাম লিখক।"
355
+
356
  user_id = get_or_create_user(username)
357
+ if not user_id:
358
+ return {}, "⚠️ লগিন কৰোঁতে সমস্যা হ'ল।"
359
+
360
  user_state = {"username": username, "user_id": user_id}
361
  total, math_count = get_user_stats(user_id)
362
  stats = (
 
370
  if chat_history is None:
371
  chat_history = []
372
 
373
+ # Check if user is logged in
374
  if not user_state or not user_state.get("user_id"):
375
+ chat_history.append([text_input or "", "⚠️ প্ৰথমে ওপৰত আপোনাৰ নাম লিখি **Login / লগিন** টিপক।"])
376
+ return chat_history, user_state
 
377
 
378
  user_id = user_state["user_id"]
379
  final_query_parts = []
380
 
381
  # Process image OCR
 
382
  if image_input is not None:
383
  ocr_text = ocr_from_image(image_input)
384
  if ocr_text:
385
+ final_query_parts.append(f"[ছবিৰ পাঠ] {ocr_text}")
386
 
387
+ if text_input and text_input.strip():
388
+ final_query_parts.append(text_input.strip())
389
 
390
  if not final_query_parts:
391
+ chat_history.append(["", "⚠️ অনুগ্ৰহ কৰি প্ৰশ্ন লিখক, কিম্বা ছবি আপলোড কৰক।"])
392
+ return chat_history, user_state
 
393
 
394
  full_query = "\n".join(final_query_parts)
395
 
 
 
 
 
 
 
 
 
396
  is_math = is_likely_math(full_query)
397
 
398
  if is_math:
399
  math_answer = solve_math_expression(full_query)
400
+ # Combine math solution with request for explanation
401
+ combined_question = f"{full_query}\n\nগণিত সমাধান:\n{math_answer}\n\nঅনুগ্ৰহ কৰি ইয়াক সহজ ভাষাত ব্যাখ্যা কৰক:"
402
+ final_answer = llm_answer_with_rag(combined_question, chat_history)
 
 
403
  else:
404
+ final_answer = llm_answer_with_rag(full_query, chat_history)
405
 
406
  log_interaction(user_id, full_query, final_answer, is_math)
407
 
408
+ display_question = text_input or "[ছবিৰ প্ৰশ্ন]"
409
  chat_history.append([display_question, final_answer])
410
 
411
+ return chat_history, user_state
412
 
413
+ def clear_chat():
414
+ return [], None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
 
416
+ # -------------------- Gradio UI --------------------
417
+ with gr.Blocks(
418
+ title=APP_NAME,
419
+ css="""
420
+ .stats-box {
421
+ background: #f0f8ff;
422
+ padding: 15px;
423
+ border-radius: 8px;
424
+ border: 1px solid #d1e7ff;
425
+ margin-bottom: 15px;
426
+ }
427
+ .login-section {
428
+ background: #f8f9fa;
429
+ padding: 15px;
430
+ border-radius: 8px;
431
+ margin-bottom: 15px;
432
+ }
433
+ """
434
+ ) as demo:
435
+ gr.Markdown(f"# 🧭 {APP_NAME}")
436
+
437
+ gr.Markdown("""
438
+ - SEBA Class 10 PDFs upload to `pdfs/class10` folder
439
+ - Text + Image (OCR) input support
440
+ - Math step-by-step solutions
441
+ - User login + progress tracking
442
+ """)
443
+
444
+ # Use a simpler state management approach
445
+ user_state = gr.State(value={})
446
+
447
  with gr.Row():
448
  with gr.Column(scale=1):
449
+ with gr.Group(elem_classes="login-section"):
450
+ gr.Markdown("### 👤 লগিন")
451
+ username_inp = gr.Textbox(
452
+ label="নাম / ইউজাৰ আইডি",
453
+ placeholder="উদাহৰণ: abu10, student01 ...",
454
+ max_lines=1
455
+ )
456
+ login_btn = gr.Button("✅ Login / লগিন", variant="primary")
457
+ stats_md = gr.Markdown("এতিয়ালৈকে লগিন হোৱা নাই।", elem_classes="stats-box")
458
+
459
+ gr.Markdown("""
460
+ ### 💡 টিপছ
461
+ - "ক্লাছ ১০ গণিত: উদাহৰণ ৩.১ প্ৰশ্ন ২" এই ধৰণৰ প্ৰশ্ন ভাল
462
+ - ফটো আপলোড কৰিলে টেক্স্টটো OCR কৰি পঢ়িব চেষ্টা কৰা হয়
463
+ - সম্ভৱ হলে প্ৰশ্নটো অসমীয়াত সোধক 🙂
464
+ """)
465
 
466
  with gr.Column(scale=3):
467
+ chatbot = gr.Chatbot(
468
+ label="জাজাবৰ সৈতে কথোপকথন",
469
+ height=500,
470
+ show_copy_button=True
 
471
  )
472
+
473
+ with gr.Row():
474
+ text_inp = gr.Textbox(
475
+ label="আপোনাৰ প্ৰশ্ন লিখক",
476
+ placeholder='উদাহৰণ: "ক্লাছ ১০ অসমীয়া: অনুচ্ছেদ পাঠ ১ ৰ মূল বিষয় কি?"',
477
+ lines=2,
478
+ scale=4
479
+ )
480
+
481
  with gr.Row():
482
+ image_inp = gr.Image(
483
+ label="📷 প্ৰশ্নৰ ছবি (Optional)",
484
+ type="filepath",
485
+ scale=3
486
+ )
487
 
488
  with gr.Row():
489
+ ask_btn = gr.Button("🤖 জাজাবৰক সোধক", variant="primary", scale=2)
490
+ clear_btn = gr.Button("🧹 পৰিষ্কাৰ কৰক", variant="secondary", scale=1)
491
 
492
+ # Event handlers
493
  login_btn.click(
494
  login_user,
495
+ inputs=[username_inp],
496
+ outputs=[user_state, stats_md]
497
  )
498
+
499
+ # Chat function - simplified
500
+ def process_chat(text, image, history, state):
501
+ return chat_logic(text, image, history, state)
502
+
503
  ask_btn.click(
504
+ process_chat,
505
+ inputs=[text_inp, image_inp, chatbot, user_state],
506
+ outputs=[chatbot, user_state]
 
 
507
  ).then(
508
+ lambda: ("", None),
509
+ outputs=[text_inp, image_inp]
510
  )
511
+
 
512
  text_inp.submit(
513
+ process_chat,
514
+ inputs=[text_inp, image_inp, chatbot, user_state],
515
+ outputs=[chatbot, user_state]
 
 
516
  ).then(
517
+ lambda: ("", None),
518
+ outputs=[text_inp, image_inp]
519
+ )
520
+
521
+ clear_btn.click(
522
+ clear_chat,
523
+ outputs=[chatbot, image_inp]
524
  )
 
 
 
 
 
525
 
526
  if __name__ == "__main__":
527
+ # For Hugging Face Spaces, don't use share=True
528
+ try:
529
+ demo.launch(
530
+ server_name="0.0.0.0",
531
+ server_port=7860,
532
+ share=False, # Changed to False for Hugging Face Spaces
533
+ show_error=True
534
+ )
535
+ except Exception as e:
536
+ print(f"Launch error: {e}")
537
+ # Fallback to simple launch
538
+ demo.launch(share=False)