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(
|