Dannyar608 commited on
Commit
0459869
·
verified ·
1 Parent(s): 97d65ae

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -75
app.py CHANGED
@@ -18,6 +18,8 @@ from huggingface_hub import HfApi, HfFolder
18
  import torch
19
  from transformers import AutoTokenizer, AutoModelForCausalLM
20
  import time
 
 
21
 
22
  # ========== CONFIGURATION ==========
23
  PROFILES_DIR = "student_profiles"
@@ -28,6 +30,9 @@ MAX_AGE = 120
28
  SESSION_TOKEN_LENGTH = 32
29
  HF_TOKEN = os.getenv("HF_TOKEN")
30
 
 
 
 
31
  # Model configuration
32
  MODEL_CHOICES = {
33
  "TinyLlama (Fastest)": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
@@ -59,38 +64,56 @@ class ModelLoader:
59
  self.loading = True
60
  self.error = None
61
  try:
62
- progress(0, desc=f"Loading {model_name}...")
63
 
64
  # Clear previous model if any
65
  if self.model:
66
  del self.model
67
  del self.tokenizer
68
  torch.cuda.empty_cache()
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- # Load tokenizer first
71
  self.tokenizer = AutoTokenizer.from_pretrained(
72
  MODEL_CHOICES[model_name],
73
  trust_remote_code=True
74
  )
75
- progress(0.3, desc="Loaded tokenizer...")
76
 
77
- # Load model with appropriate settings
78
  self.model = AutoModelForCausalLM.from_pretrained(
79
  MODEL_CHOICES[model_name],
80
- trust_remote_code=True,
81
- torch_dtype=torch.float16,
82
- device_map="auto" if torch.cuda.is_available() else None,
83
- low_cpu_mem_usage=True
84
  )
85
 
 
 
 
 
 
 
86
  progress(0.9, desc="Finalizing...")
87
  self.loaded = True
88
  self.current_model = model_name
89
  return self.model, self.tokenizer
90
 
 
 
 
 
91
  except Exception as e:
92
  self.error = str(e)
93
- print(f"Error loading model: {self.error}")
94
  return None, None
95
  finally:
96
  self.loading = False
@@ -132,7 +155,7 @@ def validate_age(age: Union[int, float, str]) -> int:
132
  def validate_file(file_obj) -> None:
133
  """Validate uploaded file."""
134
  if not file_obj:
135
- raise gr.Error("No file uploaded")
136
 
137
  file_ext = os.path.splitext(file_obj.name)[1].lower()
138
  if file_ext not in ALLOWED_FILE_TYPES:
@@ -157,7 +180,7 @@ def extract_text_from_file(file_path: str, file_ext: str) -> str:
157
  if not text.strip():
158
  raise ValueError("PyMuPDF returned empty text")
159
  except Exception as e:
160
- print(f"PyMuPDF failed, trying OCR fallback: {str(e)}")
161
  text = extract_text_from_pdf_with_ocr(file_path)
162
 
163
  elif file_ext in ['.png', '.jpg', '.jpeg']:
@@ -172,7 +195,8 @@ def extract_text_from_file(file_path: str, file_ext: str) -> str:
172
  return text
173
 
174
  except Exception as e:
175
- raise gr.Error(f"Text extraction error: {str(e)}")
 
176
 
177
  def extract_text_from_pdf_with_ocr(file_path: str) -> str:
178
  """Fallback PDF text extraction using OCR."""
@@ -182,7 +206,10 @@ def extract_text_from_pdf_with_ocr(file_path: str) -> str:
182
  for page in doc:
183
  pix = page.get_pixmap()
184
  img = Image.open(io.BytesIO(pix.tobytes()))
185
- text += pytesseract.image_to_string(img) + '\n'
 
 
 
186
  except Exception as e:
187
  raise ValueError(f"PDF OCR failed: {str(e)}")
188
  return text
@@ -192,7 +219,7 @@ def extract_text_with_ocr(file_path: str) -> str:
192
  try:
193
  image = Image.open(file_path)
194
 
195
- # Preprocess image for better OCR results
196
  image = image.convert('L') # Convert to grayscale
197
  image = image.point(lambda x: 0 if x < 128 else 255, '1') # Thresholding
198
 
@@ -355,6 +382,10 @@ class TranscriptParser:
355
  "completion_status": self._calculate_completion()
356
  }, indent=2)
357
 
 
 
 
 
358
  def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
359
  """Use AI model to parse transcript text with progress feedback"""
360
  model, tokenizer = model_loader.load_model(model_loader.current_model or DEFAULT_MODEL, progress)
@@ -393,7 +424,7 @@ def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
393
  return validate_parsed_data(formatted_data)
394
 
395
  except Exception as e:
396
- print(f"Structured parsing failed, falling back to AI: {str(e)}")
397
  # Fall back to AI parsing if structured parsing fails
398
  return parse_transcript_with_ai_fallback(text, progress)
399
 
@@ -424,10 +455,10 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
424
  progress(0.1, desc="Processing transcript with AI...")
425
 
426
  # Tokenize and generate response
427
- inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
428
  progress(0.4)
429
 
430
- outputs = model.generate(
431
  **inputs,
432
  max_new_tokens=1500,
433
  temperature=0.1,
@@ -436,20 +467,24 @@ def parse_transcript_with_ai_fallback(text: str, progress=gr.Progress()) -> Dict
436
  progress(0.8)
437
 
438
  # Decode the response
439
- response = tokenizer.decode(outputs[0], skip_special_tokens=True)
440
 
441
  # Extract JSON from response
442
- json_str = response.split('```json')[1].split('```')[0].strip() if '```json' in response else response
 
 
 
 
 
 
443
 
444
- # Parse and validate
445
- parsed_data = json.loads(json_str)
446
  progress(1.0)
447
-
448
  return validate_parsed_data(parsed_data)
449
 
450
  except torch.cuda.OutOfMemoryError:
451
  raise gr.Error("The model ran out of memory. Try with a smaller transcript or use a smaller model.")
452
  except Exception as e:
 
453
  raise gr.Error(f"Error processing transcript: {str(e)}")
454
 
455
  def validate_parsed_data(data: Dict) -> Dict:
@@ -546,6 +581,7 @@ def parse_transcript(file_obj, progress=gr.Progress()) -> Tuple[str, Optional[Di
546
  return output_text, transcript_data
547
 
548
  except Exception as e:
 
549
  return f"Error processing transcript: {str(e)}", None
550
 
551
  # ========== LEARNING STYLE QUIZ ==========
@@ -801,11 +837,12 @@ class ProfileManager:
801
  repo_type="dataset"
802
  )
803
  except Exception as e:
804
- print(f"Failed to upload to HF Hub: {str(e)}")
805
 
806
  return self._generate_profile_summary(data)
807
 
808
  except Exception as e:
 
809
  raise gr.Error(f"Error saving profile: {str(e)}")
810
 
811
  def load_profile(self, name: str = None, session_token: str = None) -> Dict:
@@ -850,7 +887,7 @@ class ProfileManager:
850
  return json.load(f)
851
 
852
  except Exception as e:
853
- print(f"Error loading profile: {str(e)}")
854
  return {}
855
 
856
  def list_profiles(self, session_token: str = None) -> List[str]:
@@ -879,7 +916,7 @@ class ProfileManager:
879
  markdown = f"""## Student Profile: {data['name']}
880
  ### Basic Information
881
  - **Age:** {data['age']}
882
- - **Interests:** {data['interests']}
883
  - **Learning Style:** {learning_style.split('##')[0].strip()}
884
  ### Academic Information
885
  {self._format_transcript(transcript)}
@@ -965,7 +1002,7 @@ class TeachingAssistant:
965
  return response
966
 
967
  except Exception as e:
968
- print(f"Error generating response: {str(e)}")
969
  return "I encountered an error processing your request. Please try again."
970
 
971
  def _update_context(self, message: str, history: List[List[Union[str, None]]]) -> None:
@@ -1327,21 +1364,33 @@ def create_interface():
1327
  transcript_data = gr.State()
1328
 
1329
  def process_transcript_and_update(file_obj, current_tab_status, progress=gr.Progress()):
1330
- output_text, data = parse_transcript(file_obj, progress)
1331
- if "Error" not in output_text:
1332
- new_status = current_tab_status.copy()
1333
- new_status[0] = True
1334
- return output_text, data, new_status, \
1335
- gr.update(elem_classes="completed-tab"), \
1336
- gr.update(interactive=True), \
1337
- gr.update(visible=False)
1338
- return output_text, data, current_tab_status, \
1339
- gr.update(), gr.update(), gr.update()
 
 
 
 
 
 
 
 
 
 
 
1340
 
1341
  upload_btn.click(
1342
  fn=process_transcript_and_update,
1343
  inputs=[transcript_file, tab_completed],
1344
- outputs=[transcript_output, transcript_data, tab_completed, step1, step2, nav_message]
 
1345
  )
1346
 
1347
  # ===== TAB 2: Learning Style Quiz =====
@@ -1388,18 +1437,28 @@ def create_interface():
1388
  current_tab_status = args[0]
1389
  answers = args[1:]
1390
 
1391
- result = learning_style_quiz.evaluate_quiz(*answers)
1392
-
1393
- new_status = current_tab_status.copy()
1394
- new_status[1] = True
1395
-
1396
- return result, \
1397
- gr.update(visible=True), \
1398
- new_status, \
1399
- gr.update(elem_classes="completed-tab"), \
1400
- gr.update(interactive=True), \
1401
- gr.update(value="<div class='alert-box'>Quiz submitted successfully! Scroll down to view your results.</div>", visible=True), \
1402
- gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
1403
 
1404
  quiz_submit.click(
1405
  fn=submit_quiz_and_update,
@@ -1456,11 +1515,26 @@ def create_interface():
1456
  )
1457
 
1458
  def save_personal_info(name, age, interests, current_tab_status):
1459
- if name.strip() and age and interests.strip():
 
 
 
 
1460
  new_status = current_tab_status.copy()
1461
  new_status[2] = True
1462
- return new_status, gr.update(elem_classes="completed-tab"), gr.update(interactive=True), gr.update(value="<div class='alert-box'>Information saved!</div>", visible=True), gr.update(visible=False)
1463
- return current_tab_status, gr.update(), gr.update(), gr.update(visible=False), gr.update(visible=True)
 
 
 
 
 
 
 
 
 
 
 
1464
 
1465
  save_personal_btn.click(
1466
  fn=save_personal_info,
@@ -1502,14 +1576,28 @@ def create_interface():
1502
  inputs = args[:-1] # All except the last which is tab_completed
1503
  current_tab_status = args[-1]
1504
 
1505
- # Call the original save function
1506
- summary = profile_manager.save_profile(*inputs)
1507
-
1508
- # Update completion status
1509
- new_status = current_tab_status.copy()
1510
- new_status[3] = True
1511
-
1512
- return summary, new_status, gr.update(elem_classes="completed-tab"), gr.update(interactive=True), gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1513
 
1514
  save_btn.click(
1515
  fn=save_profile_and_update,
@@ -1524,10 +1612,10 @@ def create_interface():
1524
  fn=lambda: profile_manager.list_profiles(session_token.value),
1525
  outputs=load_profile_dropdown
1526
  ).then(
1527
- fn=lambda: gr.update(visible=True),
1528
  outputs=load_btn
1529
  ).then(
1530
- fn=lambda: gr.update(visible=True),
1531
  outputs=delete_btn
1532
  )
1533
 
@@ -1548,6 +1636,7 @@ def create_interface():
1548
  profile_path.unlink()
1549
  return "Profile deleted successfully", ""
1550
  except Exception as e:
 
1551
  raise gr.Error(f"Error deleting profile: {str(e)}")
1552
 
1553
  delete_btn.click(
@@ -1608,41 +1697,44 @@ def create_interface():
1608
 
1609
  # Tab navigation logic with completion check
1610
  def navigate_to_tab(tab_index: int, tab_completed_status):
1611
- # Always allow going back to previous tabs
1612
  current_tab = tabs.selected
1613
- if current_tab is not None and tab_index > current_tab:
1614
- # Check if current tab is completed
1615
- if not tab_completed_status.get(current_tab, False):
1616
- return gr.Tabs(selected=current_tab), \
1617
- gr.update(value=f"<div class='nav-message'>Please complete the current tab before proceeding to tab {tab_index + 1}</div>", visible=True), \
1618
- gr.update(visible=False)
1619
 
1620
- return gr.Tabs(selected=tab_index), gr.update(visible=False), gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
1621
 
1622
  step1.click(
1623
  fn=lambda idx, status: navigate_to_tab(idx, status),
1624
  inputs=[gr.State(0), tab_completed],
1625
- outputs=[tabs, nav_message, quiz_alert]
1626
  )
1627
  step2.click(
1628
  fn=lambda idx, status: navigate_to_tab(idx, status),
1629
  inputs=[gr.State(1), tab_completed],
1630
- outputs=[tabs, nav_message, quiz_alert]
1631
  )
1632
  step3.click(
1633
  fn=lambda idx, status: navigate_to_tab(idx, status),
1634
  inputs=[gr.State(2), tab_completed],
1635
- outputs=[tabs, nav_message, quiz_alert]
1636
  )
1637
  step4.click(
1638
  fn=lambda idx, status: navigate_to_tab(idx, status),
1639
  inputs=[gr.State(3), tab_completed],
1640
- outputs=[tabs, nav_message, quiz_alert]
1641
  )
1642
  step5.click(
1643
  fn=lambda idx, status: navigate_to_tab(idx, status),
1644
  inputs=[gr.State(4), tab_completed],
1645
- outputs=[tabs, nav_message, quiz_alert]
1646
  )
1647
 
1648
  # Model loading functions
@@ -1654,6 +1746,7 @@ def create_interface():
1654
  else:
1655
  return gr.update(value=f"<div class='nav-message'>Failed to load model: {model_loader.error}</div>", visible=True)
1656
  except Exception as e:
 
1657
  return gr.update(value=f"<div class='nav-message'>Error: {str(e)}</div>", visible=True)
1658
 
1659
  load_model_btn.click(
 
18
  import torch
19
  from transformers import AutoTokenizer, AutoModelForCausalLM
20
  import time
21
+ import logging
22
+ import asyncio
23
 
24
  # ========== CONFIGURATION ==========
25
  PROFILES_DIR = "student_profiles"
 
30
  SESSION_TOKEN_LENGTH = 32
31
  HF_TOKEN = os.getenv("HF_TOKEN")
32
 
33
+ # Initialize logging
34
+ logging.basicConfig(filename='app.log', level=logging.INFO)
35
+
36
  # Model configuration
37
  MODEL_CHOICES = {
38
  "TinyLlama (Fastest)": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
 
64
  self.loading = True
65
  self.error = None
66
  try:
67
+ progress(0.1, desc="Initializing...")
68
 
69
  # Clear previous model if any
70
  if self.model:
71
  del self.model
72
  del self.tokenizer
73
  torch.cuda.empty_cache()
74
+ time.sleep(2) # Allow CUDA cleanup
75
+
76
+ # Load with optimized settings
77
+ model_kwargs = {
78
+ "trust_remote_code": True,
79
+ "torch_dtype": torch.float16,
80
+ "device_map": "auto",
81
+ "low_cpu_mem_usage": True
82
+ }
83
+
84
+ if "TinyLlama" in model_name:
85
+ model_kwargs["attn_implementation"] = "flash_attention_2"
86
 
87
+ progress(0.3, desc="Loading tokenizer...")
88
  self.tokenizer = AutoTokenizer.from_pretrained(
89
  MODEL_CHOICES[model_name],
90
  trust_remote_code=True
91
  )
 
92
 
93
+ progress(0.6, desc="Loading model...")
94
  self.model = AutoModelForCausalLM.from_pretrained(
95
  MODEL_CHOICES[model_name],
96
+ **model_kwargs
 
 
 
97
  )
98
 
99
+ # Verify model responsiveness
100
+ progress(0.8, desc="Verifying model...")
101
+ test_input = self.tokenizer("Test", return_tensors="pt").to(self.model.device)
102
+ _ = self.model.generate(**test_input, max_new_tokens=1)
103
+
104
+ self.model.eval() # Disable dropout
105
  progress(0.9, desc="Finalizing...")
106
  self.loaded = True
107
  self.current_model = model_name
108
  return self.model, self.tokenizer
109
 
110
+ except torch.cuda.OutOfMemoryError:
111
+ self.error = "Out of GPU memory. Try a smaller model."
112
+ logging.error(self.error)
113
+ return None, None
114
  except Exception as e:
115
  self.error = str(e)
116
+ logging.error(f"Model loading error: {self.error}")
117
  return None, None
118
  finally:
119
  self.loading = False
 
155
  def validate_file(file_obj) -> None:
156
  """Validate uploaded file."""
157
  if not file_obj:
158
+ raise ValueError("No file uploaded")
159
 
160
  file_ext = os.path.splitext(file_obj.name)[1].lower()
161
  if file_ext not in ALLOWED_FILE_TYPES:
 
180
  if not text.strip():
181
  raise ValueError("PyMuPDF returned empty text")
182
  except Exception as e:
183
+ logging.warning(f"PyMuPDF failed: {str(e)}. Trying OCR fallback...")
184
  text = extract_text_from_pdf_with_ocr(file_path)
185
 
186
  elif file_ext in ['.png', '.jpg', '.jpeg']:
 
195
  return text
196
 
197
  except Exception as e:
198
+ logging.error(f"Text extraction error: {str(e)}")
199
+ raise gr.Error(f"Text extraction error: {str(e)}\nTips: Use high-quality images/PDFs with clear text.")
200
 
201
  def extract_text_from_pdf_with_ocr(file_path: str) -> str:
202
  """Fallback PDF text extraction using OCR."""
 
206
  for page in doc:
207
  pix = page.get_pixmap()
208
  img = Image.open(io.BytesIO(pix.tobytes()))
209
+ # Preprocess image for better OCR
210
+ img = img.convert('L') # Grayscale
211
+ img = img.point(lambda x: 0 if x < 128 else 255) # Binarize
212
+ text += pytesseract.image_to_string(img, config='--psm 6 --oem 3') + '\n'
213
  except Exception as e:
214
  raise ValueError(f"PDF OCR failed: {str(e)}")
215
  return text
 
219
  try:
220
  image = Image.open(file_path)
221
 
222
+ # Enhanced preprocessing
223
  image = image.convert('L') # Convert to grayscale
224
  image = image.point(lambda x: 0 if x < 128 else 255, '1') # Thresholding
225
 
 
382
  "completion_status": self._calculate_completion()
383
  }, indent=2)
384
 
385
+ async def parse_transcript_async(file_obj, progress=gr.Progress()):
386
+ """Async wrapper for transcript parsing"""
387
+ return await asyncio.to_thread(parse_transcript, file_obj, progress)
388
+
389
  def parse_transcript_with_ai(text: str, progress=gr.Progress()) -> Dict:
390
  """Use AI model to parse transcript text with progress feedback"""
391
  model, tokenizer = model_loader.load_model(model_loader.current_model or DEFAULT_MODEL, progress)
 
424
  return validate_parsed_data(formatted_data)
425
 
426
  except Exception as e:
427
+ logging.warning(f"Structured parsing failed, falling back to AI: {str(e)}")
428
  # Fall back to AI parsing if structured parsing fails
429
  return parse_transcript_with_ai_fallback(text, progress)
430
 
 
455
  progress(0.1, desc="Processing transcript with AI...")
456
 
457
  # Tokenize and generate response
458
+ inputs = model_loader.tokenizer(prompt, return_tensors="pt").to(model_loader.model.device)
459
  progress(0.4)
460
 
461
+ outputs = model_loader.model.generate(
462
  **inputs,
463
  max_new_tokens=1500,
464
  temperature=0.1,
 
467
  progress(0.8)
468
 
469
  # Decode the response
470
+ response = model_loader.tokenizer.decode(outputs[0], skip_special_tokens=True)
471
 
472
  # Extract JSON from response
473
+ try:
474
+ json_str = response.split('```json')[1].split('```')[0].strip()
475
+ parsed_data = json.loads(json_str)
476
+ except (IndexError, json.JSONDecodeError):
477
+ # Fallback: Extract JSON-like substring
478
+ json_str = re.search(r'\{.*\}', response, re.DOTALL).group()
479
+ parsed_data = json.loads(json_str)
480
 
 
 
481
  progress(1.0)
 
482
  return validate_parsed_data(parsed_data)
483
 
484
  except torch.cuda.OutOfMemoryError:
485
  raise gr.Error("The model ran out of memory. Try with a smaller transcript or use a smaller model.")
486
  except Exception as e:
487
+ logging.error(f"AI parsing error: {str(e)}")
488
  raise gr.Error(f"Error processing transcript: {str(e)}")
489
 
490
  def validate_parsed_data(data: Dict) -> Dict:
 
581
  return output_text, transcript_data
582
 
583
  except Exception as e:
584
+ logging.error(f"Transcript processing error: {str(e)}")
585
  return f"Error processing transcript: {str(e)}", None
586
 
587
  # ========== LEARNING STYLE QUIZ ==========
 
837
  repo_type="dataset"
838
  )
839
  except Exception as e:
840
+ logging.error(f"Failed to upload to HF Hub: {str(e)}")
841
 
842
  return self._generate_profile_summary(data)
843
 
844
  except Exception as e:
845
+ logging.error(f"Error saving profile: {str(e)}")
846
  raise gr.Error(f"Error saving profile: {str(e)}")
847
 
848
  def load_profile(self, name: str = None, session_token: str = None) -> Dict:
 
887
  return json.load(f)
888
 
889
  except Exception as e:
890
+ logging.error(f"Error loading profile: {str(e)}")
891
  return {}
892
 
893
  def list_profiles(self, session_token: str = None) -> List[str]:
 
916
  markdown = f"""## Student Profile: {data['name']}
917
  ### Basic Information
918
  - **Age:** {data['age']}
919
+ - **Interests:** {data.get('interests', 'Not specified')}
920
  - **Learning Style:** {learning_style.split('##')[0].strip()}
921
  ### Academic Information
922
  {self._format_transcript(transcript)}
 
1002
  return response
1003
 
1004
  except Exception as e:
1005
+ logging.error(f"Error generating response: {str(e)}")
1006
  return "I encountered an error processing your request. Please try again."
1007
 
1008
  def _update_context(self, message: str, history: List[List[Union[str, None]]]) -> None:
 
1364
  transcript_data = gr.State()
1365
 
1366
  def process_transcript_and_update(file_obj, current_tab_status, progress=gr.Progress()):
1367
+ try:
1368
+ output_text, data = parse_transcript(file_obj, progress)
1369
+ if "Error" not in output_text:
1370
+ new_status = current_tab_status.copy()
1371
+ new_status[0] = True
1372
+ return (
1373
+ output_text,
1374
+ data,
1375
+ new_status,
1376
+ gr.update(elem_classes="completed-tab"),
1377
+ gr.update(interactive=True),
1378
+ gr.update(visible=False)
1379
+ except Exception as e:
1380
+ logging.error(f"Upload error: {str(e)}")
1381
+ return (
1382
+ "Error processing transcript. Please try again.",
1383
+ None,
1384
+ current_tab_status,
1385
+ gr.update(),
1386
+ gr.update(),
1387
+ gr.update(visible=True, value=f"<div class='nav-message'>Error: {str(e)}</div>"))
1388
 
1389
  upload_btn.click(
1390
  fn=process_transcript_and_update,
1391
  inputs=[transcript_file, tab_completed],
1392
+ outputs=[transcript_output, transcript_data, tab_completed, step1, step2, nav_message],
1393
+ concurrency_limit=1
1394
  )
1395
 
1396
  # ===== TAB 2: Learning Style Quiz =====
 
1437
  current_tab_status = args[0]
1438
  answers = args[1:]
1439
 
1440
+ try:
1441
+ result = learning_style_quiz.evaluate_quiz(*answers)
1442
+ new_status = current_tab_status.copy()
1443
+ new_status[1] = True
1444
+ return (
1445
+ result,
1446
+ gr.update(visible=True),
1447
+ new_status,
1448
+ gr.update(elem_classes="completed-tab"),
1449
+ gr.update(interactive=True),
1450
+ gr.update(value="<div class='alert-box'>Quiz submitted successfully! Scroll down to view your results.</div>", visible=True),
1451
+ gr.update(visible=False))
1452
+ except Exception as e:
1453
+ logging.error(f"Quiz error: {str(e)}")
1454
+ return (
1455
+ f"Error evaluating quiz: {str(e)}",
1456
+ gr.update(visible=True),
1457
+ current_tab_status,
1458
+ gr.update(),
1459
+ gr.update(),
1460
+ gr.update(value=f"<div class='nav-message'>Error: {str(e)}</div>", visible=True),
1461
+ gr.update(visible=False))
1462
 
1463
  quiz_submit.click(
1464
  fn=submit_quiz_and_update,
 
1515
  )
1516
 
1517
  def save_personal_info(name, age, interests, current_tab_status):
1518
+ try:
1519
+ name = validate_name(name)
1520
+ age = validate_age(age)
1521
+ interests = sanitize_input(interests)
1522
+
1523
  new_status = current_tab_status.copy()
1524
  new_status[2] = True
1525
+ return (
1526
+ new_status,
1527
+ gr.update(elem_classes="completed-tab"),
1528
+ gr.update(interactive=True),
1529
+ gr.update(value="<div class='alert-box'>Information saved!</div>", visible=True),
1530
+ gr.update(visible=False))
1531
+ except Exception as e:
1532
+ return (
1533
+ current_tab_status,
1534
+ gr.update(),
1535
+ gr.update(),
1536
+ gr.update(visible=False),
1537
+ gr.update(visible=True, value=f"<div class='nav-message'>Error: {str(e)}</div>"))
1538
 
1539
  save_personal_btn.click(
1540
  fn=save_personal_info,
 
1576
  inputs = args[:-1] # All except the last which is tab_completed
1577
  current_tab_status = args[-1]
1578
 
1579
+ try:
1580
+ # Call the original save function
1581
+ summary = profile_manager.save_profile(*inputs)
1582
+
1583
+ # Update completion status
1584
+ new_status = current_tab_status.copy()
1585
+ new_status[3] = True
1586
+
1587
+ return (
1588
+ summary,
1589
+ new_status,
1590
+ gr.update(elem_classes="completed-tab"),
1591
+ gr.update(interactive=True),
1592
+ gr.update(visible=False))
1593
+ except Exception as e:
1594
+ logging.error(f"Save profile error: {str(e)}")
1595
+ return (
1596
+ f"Error saving profile: {str(e)}",
1597
+ current_tab_status,
1598
+ gr.update(),
1599
+ gr.update(),
1600
+ gr.update(visible=True, value=f"<div class='nav-message'>Error: {str(e)}</div>"))
1601
 
1602
  save_btn.click(
1603
  fn=save_profile_and_update,
 
1612
  fn=lambda: profile_manager.list_profiles(session_token.value),
1613
  outputs=load_profile_dropdown
1614
  ).then(
1615
+ fn=lambda: gr.update(visible=bool(profile_manager.list_profiles(session_token.value))),
1616
  outputs=load_btn
1617
  ).then(
1618
+ fn=lambda: gr.update(visible=bool(profile_manager.list_profiles(session_token.value))),
1619
  outputs=delete_btn
1620
  )
1621
 
 
1636
  profile_path.unlink()
1637
  return "Profile deleted successfully", ""
1638
  except Exception as e:
1639
+ logging.error(f"Delete profile error: {str(e)}")
1640
  raise gr.Error(f"Error deleting profile: {str(e)}")
1641
 
1642
  delete_btn.click(
 
1697
 
1698
  # Tab navigation logic with completion check
1699
  def navigate_to_tab(tab_index: int, tab_completed_status):
 
1700
  current_tab = tabs.selected
 
 
 
 
 
 
1701
 
1702
+ # Allow backward navigation
1703
+ if tab_index <= current_tab:
1704
+ return gr.Tabs(selected=tab_index), gr.update(visible=False)
1705
+
1706
+ # Check if current tab is completed
1707
+ if not tab_completed_status.get(current_tab, False):
1708
+ return (
1709
+ gr.Tabs(selected=current_tab),
1710
+ gr.update(value=f"⚠️ Complete Step {current_tab+1} first!", visible=True))
1711
+
1712
+ return gr.Tabs(selected=tab_index), gr.update(visible=False)
1713
 
1714
  step1.click(
1715
  fn=lambda idx, status: navigate_to_tab(idx, status),
1716
  inputs=[gr.State(0), tab_completed],
1717
+ outputs=[tabs, nav_message]
1718
  )
1719
  step2.click(
1720
  fn=lambda idx, status: navigate_to_tab(idx, status),
1721
  inputs=[gr.State(1), tab_completed],
1722
+ outputs=[tabs, nav_message]
1723
  )
1724
  step3.click(
1725
  fn=lambda idx, status: navigate_to_tab(idx, status),
1726
  inputs=[gr.State(2), tab_completed],
1727
+ outputs=[tabs, nav_message]
1728
  )
1729
  step4.click(
1730
  fn=lambda idx, status: navigate_to_tab(idx, status),
1731
  inputs=[gr.State(3), tab_completed],
1732
+ outputs=[tabs, nav_message]
1733
  )
1734
  step5.click(
1735
  fn=lambda idx, status: navigate_to_tab(idx, status),
1736
  inputs=[gr.State(4), tab_completed],
1737
+ outputs=[tabs, nav_message]
1738
  )
1739
 
1740
  # Model loading functions
 
1746
  else:
1747
  return gr.update(value=f"<div class='nav-message'>Failed to load model: {model_loader.error}</div>", visible=True)
1748
  except Exception as e:
1749
+ logging.error(f"Model loading error: {str(e)}")
1750
  return gr.update(value=f"<div class='nav-message'>Error: {str(e)}</div>", visible=True)
1751
 
1752
  load_model_btn.click(