Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -21,6 +21,17 @@ def sanitize_input(text: str) -> str:
|
|
| 21 |
"""Sanitize user input to prevent XSS and injection attacks."""
|
| 22 |
return html.escape(text.strip())
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
def validate_age(age: Union[int, float, str]) -> int:
|
| 25 |
"""Validate and convert age input."""
|
| 26 |
try:
|
|
@@ -36,12 +47,13 @@ def validate_file(file_obj) -> None:
|
|
| 36 |
if not file_obj:
|
| 37 |
raise gr.Error("No file uploaded")
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 41 |
|
| 42 |
file_size = os.path.getsize(file_obj.name) / (1024 * 1024) # MB
|
| 43 |
if file_size > MAX_FILE_SIZE_MB:
|
| 44 |
-
raise gr.Error(f"File
|
| 45 |
|
| 46 |
# ========== TRANSCRIPT PARSING ==========
|
| 47 |
def extract_gpa(text: str, gpa_type: str) -> str:
|
|
@@ -116,6 +128,9 @@ def extract_courses_from_table(text: str) -> Dict[str, List[Dict]]:
|
|
| 116 |
def parse_transcript(file_obj) -> Tuple[str, Optional[Dict]]:
|
| 117 |
"""Parse transcript file with robust error handling."""
|
| 118 |
try:
|
|
|
|
|
|
|
|
|
|
| 119 |
validate_file(file_obj)
|
| 120 |
|
| 121 |
text = ''
|
|
@@ -335,7 +350,7 @@ learning_style_quiz = LearningStyleQuiz()
|
|
| 335 |
class ProfileManager:
|
| 336 |
def __init__(self):
|
| 337 |
self.profiles_dir = Path(PROFILES_DIR)
|
| 338 |
-
self.profiles_dir.mkdir(exist_ok=True)
|
| 339 |
|
| 340 |
def save_profile(self, name: str, age: Union[int, str], interests: str,
|
| 341 |
transcript: Dict, learning_style: str,
|
|
@@ -345,10 +360,7 @@ class ProfileManager:
|
|
| 345 |
"""Save student profile with validation."""
|
| 346 |
try:
|
| 347 |
# Validate required fields
|
| 348 |
-
|
| 349 |
-
raise gr.Error("Name is required")
|
| 350 |
-
|
| 351 |
-
name = sanitize_input(name)
|
| 352 |
age = validate_age(age)
|
| 353 |
interests = sanitize_input(interests)
|
| 354 |
|
|
@@ -840,11 +852,11 @@ def create_interface():
|
|
| 840 |
)
|
| 841 |
|
| 842 |
quiz_submit.click(
|
| 843 |
-
fn=lambda *answers: learning_style_quiz.evaluate_quiz(answers),
|
| 844 |
inputs=quiz_components,
|
| 845 |
outputs=learning_output
|
| 846 |
).then(
|
| 847 |
-
fn=lambda: gr.
|
| 848 |
outputs=learning_output
|
| 849 |
)
|
| 850 |
|
|
@@ -907,6 +919,7 @@ def create_interface():
|
|
| 907 |
visible=bool(profile_manager.list_profiles())
|
| 908 |
)
|
| 909 |
load_btn = gr.Button("Load Profile", visible=bool(profile_manager.list_profiles()))
|
|
|
|
| 910 |
|
| 911 |
with gr.Column(scale=2):
|
| 912 |
output_summary = gr.Markdown(
|
|
@@ -929,6 +942,16 @@ def create_interface():
|
|
| 929 |
inputs=load_profile_dropdown,
|
| 930 |
outputs=output_summary
|
| 931 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 932 |
|
| 933 |
# ===== TAB 5: AI Teaching Assistant =====
|
| 934 |
with gr.Tab("AI Assistant", id=4) as tab5:
|
|
@@ -949,27 +972,27 @@ def create_interface():
|
|
| 949 |
|
| 950 |
# Tab navigation logic
|
| 951 |
def navigate_to_tab(tab_index: int):
|
| 952 |
-
return
|
| 953 |
|
| 954 |
step1.click(
|
| 955 |
fn=lambda: navigate_to_tab(0),
|
| 956 |
-
outputs=
|
| 957 |
)
|
| 958 |
step2.click(
|
| 959 |
fn=lambda: navigate_to_tab(1),
|
| 960 |
-
outputs=
|
| 961 |
)
|
| 962 |
step3.click(
|
| 963 |
fn=lambda: navigate_to_tab(2),
|
| 964 |
-
outputs=
|
| 965 |
)
|
| 966 |
step4.click(
|
| 967 |
fn=lambda: navigate_to_tab(3),
|
| 968 |
-
outputs=
|
| 969 |
)
|
| 970 |
step5.click(
|
| 971 |
fn=lambda: navigate_to_tab(4),
|
| 972 |
-
outputs=
|
| 973 |
)
|
| 974 |
|
| 975 |
return app
|
|
|
|
| 21 |
"""Sanitize user input to prevent XSS and injection attacks."""
|
| 22 |
return html.escape(text.strip())
|
| 23 |
|
| 24 |
+
def validate_name(name: str) -> str:
|
| 25 |
+
"""Validate name input."""
|
| 26 |
+
name = name.strip()
|
| 27 |
+
if not name:
|
| 28 |
+
raise gr.Error("Name cannot be empty")
|
| 29 |
+
if len(name) > 100:
|
| 30 |
+
raise gr.Error("Name is too long (max 100 characters)")
|
| 31 |
+
if any(c.isdigit() for c in name):
|
| 32 |
+
raise gr.Error("Name cannot contain numbers")
|
| 33 |
+
return name
|
| 34 |
+
|
| 35 |
def validate_age(age: Union[int, float, str]) -> int:
|
| 36 |
"""Validate and convert age input."""
|
| 37 |
try:
|
|
|
|
| 47 |
if not file_obj:
|
| 48 |
raise gr.Error("No file uploaded")
|
| 49 |
|
| 50 |
+
file_ext = os.path.splitext(file_obj.name)[1].lower()
|
| 51 |
+
if file_ext not in ALLOWED_FILE_TYPES:
|
| 52 |
+
raise gr.Error(f"Invalid file type. Allowed: {', '.join(ALLOWED_FILE_TYPES)}")
|
| 53 |
|
| 54 |
file_size = os.path.getsize(file_obj.name) / (1024 * 1024) # MB
|
| 55 |
if file_size > MAX_FILE_SIZE_MB:
|
| 56 |
+
raise gr.Error(f"File too large. Max size: {MAX_FILE_SIZE_MB}MB")
|
| 57 |
|
| 58 |
# ========== TRANSCRIPT PARSING ==========
|
| 59 |
def extract_gpa(text: str, gpa_type: str) -> str:
|
|
|
|
| 128 |
def parse_transcript(file_obj) -> Tuple[str, Optional[Dict]]:
|
| 129 |
"""Parse transcript file with robust error handling."""
|
| 130 |
try:
|
| 131 |
+
if not file_obj:
|
| 132 |
+
raise gr.Error("Please upload a file first")
|
| 133 |
+
|
| 134 |
validate_file(file_obj)
|
| 135 |
|
| 136 |
text = ''
|
|
|
|
| 350 |
class ProfileManager:
|
| 351 |
def __init__(self):
|
| 352 |
self.profiles_dir = Path(PROFILES_DIR)
|
| 353 |
+
self.profiles_dir.mkdir(exist_ok=True, parents=True)
|
| 354 |
|
| 355 |
def save_profile(self, name: str, age: Union[int, str], interests: str,
|
| 356 |
transcript: Dict, learning_style: str,
|
|
|
|
| 360 |
"""Save student profile with validation."""
|
| 361 |
try:
|
| 362 |
# Validate required fields
|
| 363 |
+
name = validate_name(name)
|
|
|
|
|
|
|
|
|
|
| 364 |
age = validate_age(age)
|
| 365 |
interests = sanitize_input(interests)
|
| 366 |
|
|
|
|
| 852 |
)
|
| 853 |
|
| 854 |
quiz_submit.click(
|
| 855 |
+
fn=lambda *answers: learning_style_quiz.evaluate_quiz(*answers),
|
| 856 |
inputs=quiz_components,
|
| 857 |
outputs=learning_output
|
| 858 |
).then(
|
| 859 |
+
fn=lambda: gr.Markdown(visible=True),
|
| 860 |
outputs=learning_output
|
| 861 |
)
|
| 862 |
|
|
|
|
| 919 |
visible=bool(profile_manager.list_profiles())
|
| 920 |
)
|
| 921 |
load_btn = gr.Button("Load Profile", visible=bool(profile_manager.list_profiles()))
|
| 922 |
+
clear_btn = gr.Button("Clear Form")
|
| 923 |
|
| 924 |
with gr.Column(scale=2):
|
| 925 |
output_summary = gr.Markdown(
|
|
|
|
| 942 |
inputs=load_profile_dropdown,
|
| 943 |
outputs=output_summary
|
| 944 |
)
|
| 945 |
+
|
| 946 |
+
clear_btn.click(
|
| 947 |
+
fn=lambda: [gr.update(value="") for _ in range(12)],
|
| 948 |
+
outputs=[
|
| 949 |
+
name, age, interests,
|
| 950 |
+
movie, movie_reason, show, show_reason,
|
| 951 |
+
book, book_reason, character, character_reason,
|
| 952 |
+
blog_text
|
| 953 |
+
]
|
| 954 |
+
)
|
| 955 |
|
| 956 |
# ===== TAB 5: AI Teaching Assistant =====
|
| 957 |
with gr.Tab("AI Assistant", id=4) as tab5:
|
|
|
|
| 972 |
|
| 973 |
# Tab navigation logic
|
| 974 |
def navigate_to_tab(tab_index: int):
|
| 975 |
+
return gr.Tabs(selected=tab_index)
|
| 976 |
|
| 977 |
step1.click(
|
| 978 |
fn=lambda: navigate_to_tab(0),
|
| 979 |
+
outputs=tabs
|
| 980 |
)
|
| 981 |
step2.click(
|
| 982 |
fn=lambda: navigate_to_tab(1),
|
| 983 |
+
outputs=tabs
|
| 984 |
)
|
| 985 |
step3.click(
|
| 986 |
fn=lambda: navigate_to_tab(2),
|
| 987 |
+
outputs=tabs
|
| 988 |
)
|
| 989 |
step4.click(
|
| 990 |
fn=lambda: navigate_to_tab(3),
|
| 991 |
+
outputs=tabs
|
| 992 |
)
|
| 993 |
step5.click(
|
| 994 |
fn=lambda: navigate_to_tab(4),
|
| 995 |
+
outputs=tabs
|
| 996 |
)
|
| 997 |
|
| 998 |
return app
|