Spaces:
Running
Running
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
|