Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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=
|
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 |
-
|
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 |
-
|
78 |
self.model = AutoModelForCausalLM.from_pretrained(
|
79 |
MODEL_CHOICES[model_name],
|
80 |
-
|
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 |
-
|
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
|
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 |
-
|
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 |
-
|
|
|
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 |
-
|
|
|
|
|
|
|
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 |
-
#
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
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
|
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 |
-
|
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 |
-
|
1331 |
-
|
1332 |
-
|
1333 |
-
|
1334 |
-
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
1339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
1392 |
-
|
1393 |
-
|
1394 |
-
|
1395 |
-
|
1396 |
-
|
1397 |
-
|
1398 |
-
|
1399 |
-
|
1400 |
-
|
1401 |
-
|
1402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
1460 |
new_status = current_tab_status.copy()
|
1461 |
new_status[2] = True
|
1462 |
-
return
|
1463 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
1506 |
-
|
1507 |
-
|
1508 |
-
|
1509 |
-
|
1510 |
-
|
1511 |
-
|
1512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=
|
1528 |
outputs=load_btn
|
1529 |
).then(
|
1530 |
-
fn=lambda: gr.update(visible=
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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
|
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
|
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
|
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
|
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(
|