Spaces:
Running
Running
Commit
Β·
62e8dda
1
Parent(s):
4b3ae07
databank added
Browse files
app.py
CHANGED
@@ -133,6 +133,48 @@ class HuggingFaceT5GEDInference:
|
|
133 |
|
134 |
return error_spans
|
135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
def _preprocess_inputs(self, text, max_length=128):
|
137 |
"""Preprocess input text exactly as during training"""
|
138 |
# Get GED predictions
|
@@ -242,19 +284,19 @@ class HuggingFaceT5GEDInference:
|
|
242 |
"""Enhanced analysis method for Gradio integration"""
|
243 |
if not text.strip():
|
244 |
return "Model not available or empty text", ""
|
245 |
-
|
246 |
try:
|
247 |
# Get corrected text
|
248 |
corrected_text = self.correct_text(text)
|
249 |
-
|
250 |
-
# Get error spans
|
251 |
error_spans = self._get_error_spans(text)
|
252 |
-
|
253 |
# Generate HTML output
|
254 |
html_output = self.generate_html_analysis(text, corrected_text, error_spans)
|
255 |
-
|
256 |
return corrected_text, html_output
|
257 |
-
|
258 |
except Exception as e:
|
259 |
return f"Error during analysis: {str(e)}", ""
|
260 |
|
@@ -317,6 +359,21 @@ class HuggingFaceT5GEDInference:
|
|
317 |
</div>
|
318 |
"""
|
319 |
return html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
|
321 |
# Initialize SQLite database for storing submissions and exercises
|
322 |
def init_database():
|
@@ -375,11 +432,154 @@ def init_database():
|
|
375 |
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
376 |
)''')
|
377 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
378 |
conn.commit()
|
379 |
conn.close()
|
380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
381 |
# Initialize database and components
|
382 |
init_database()
|
|
|
|
|
383 |
print("Initializing enhanced grammar checker...")
|
384 |
grammar_checker = HuggingFaceT5GEDInference()
|
385 |
print("Grammar checker initialized successfully!")
|
@@ -424,26 +624,49 @@ def analyze_student_writing(text, student_name, task_title="General Writing Task
|
|
424 |
|
425 |
return corrected_text, html_analysis
|
426 |
|
|
|
427 |
def create_exercise_from_text(text, exercise_title="Grammar Exercise"):
|
428 |
-
"""Create an exercise from text with errors using
|
429 |
if not text.strip():
|
430 |
return "Please enter text to create an exercise.", ""
|
431 |
|
432 |
-
# Analyze text to
|
433 |
sentences = nltk.sent_tokenize(text)
|
434 |
exercise_sentences = []
|
|
|
435 |
|
436 |
for sentence in sentences:
|
437 |
-
|
438 |
-
|
|
|
|
|
|
|
439 |
exercise_sentences.append({
|
440 |
"original": sentence.strip(),
|
441 |
-
"corrected": corrected.strip()
|
|
|
442 |
})
|
|
|
443 |
|
444 |
if not exercise_sentences:
|
445 |
return "No errors found in the text. Cannot create exercise.", ""
|
446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
447 |
# Store exercise in database
|
448 |
conn = sqlite3.connect('language_app.db')
|
449 |
c = conn.cursor()
|
@@ -452,7 +675,7 @@ def create_exercise_from_text(text, exercise_title="Grammar Exercise"):
|
|
452 |
VALUES (?, ?, ?)""",
|
453 |
(exercise_title,
|
454 |
"Correct the grammatical errors in the following sentences:",
|
455 |
-
json.dumps(
|
456 |
|
457 |
exercise_id = c.lastrowid
|
458 |
conn.commit()
|
@@ -464,15 +687,18 @@ def create_exercise_from_text(text, exercise_title="Grammar Exercise"):
|
|
464 |
<h3>{exercise_title}</h3>
|
465 |
<p><strong>Exercise ID: {exercise_id}</strong></p>
|
466 |
<p><strong>Instructions:</strong> Correct the grammatical errors in the following sentences:</p>
|
|
|
467 |
<ol>
|
468 |
"""
|
469 |
|
470 |
-
for i, sentence_data in enumerate(
|
471 |
-
|
|
|
472 |
|
473 |
exercise_html += "</ol></div>"
|
474 |
|
475 |
-
return f"Exercise created with {len(exercise_sentences)}
|
|
|
476 |
|
477 |
def attempt_exercise(exercise_id, student_responses, student_name):
|
478 |
"""Submit exercise attempt and get score using enhanced analysis"""
|
@@ -504,20 +730,28 @@ def attempt_exercise(exercise_id, student_responses, student_name):
|
|
504 |
|
505 |
# Calculate score using enhanced analysis
|
506 |
correct_count = 0
|
507 |
-
|
508 |
|
509 |
for i, (sentence_data, response) in enumerate(zip(exercise_sentences, responses), 1):
|
510 |
-
|
|
|
511 |
|
512 |
# Use the model to check if the response is correct
|
513 |
-
response_corrected,
|
514 |
is_correct = response_corrected.strip() == response.strip() # No further corrections needed
|
515 |
|
516 |
if is_correct:
|
517 |
correct_count += 1
|
518 |
-
|
519 |
-
|
520 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
|
522 |
score = (correct_count / len(exercise_sentences)) * 100
|
523 |
|
@@ -525,7 +759,7 @@ def attempt_exercise(exercise_id, student_responses, student_name):
|
|
525 |
attempt_data = {
|
526 |
"responses": responses,
|
527 |
"score": score,
|
528 |
-
"
|
529 |
}
|
530 |
|
531 |
c.execute("""INSERT INTO exercise_attempts (exercise_id, student_name, responses, score)
|
@@ -535,18 +769,185 @@ def attempt_exercise(exercise_id, student_responses, student_name):
|
|
535 |
conn.commit()
|
536 |
conn.close()
|
537 |
|
|
|
|
|
|
|
538 |
feedback_html = f"""
|
539 |
-
<div style='font-family: Arial, sans-serif;
|
540 |
-
|
541 |
-
<
|
542 |
-
|
543 |
-
{'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
544 |
</div>
|
545 |
</div>
|
546 |
"""
|
547 |
|
548 |
return f"Score: {score:.1f}%", feedback_html
|
549 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
def get_student_progress(student_name):
|
551 |
"""Get student's submission and exercise history"""
|
552 |
if not student_name.strip():
|
@@ -653,28 +1054,46 @@ with gr.Blocks(title="Language Learning App - Enhanced Grammar Checker", theme=g
|
|
653 |
# Exercise Attempt Tab
|
654 |
with gr.TabItem("βοΈ Exercise Practice"):
|
655 |
gr.Markdown("## Practice Grammar Exercises")
|
656 |
-
|
657 |
with gr.Row():
|
658 |
with gr.Column():
|
659 |
exercise_id_input = gr.Textbox(label="Exercise ID", placeholder="Enter exercise ID")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
660 |
student_name_exercise = gr.Textbox(label="Your Name", placeholder="Enter your name")
|
661 |
responses_input = gr.Textbox(
|
662 |
label="Your Answers",
|
663 |
-
lines=
|
664 |
-
placeholder="
|
665 |
)
|
666 |
-
submit_exercise_btn = gr.Button("Submit Answers", variant="primary")
|
667 |
|
668 |
with gr.Column():
|
669 |
score_output = gr.Textbox(label="Your Score")
|
670 |
feedback_output = gr.HTML(label="Detailed Feedback")
|
671 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
672 |
submit_exercise_btn.click(
|
673 |
attempt_exercise,
|
674 |
inputs=[exercise_id_input, responses_input, student_name_exercise],
|
675 |
outputs=[score_output, feedback_output]
|
676 |
)
|
677 |
-
|
678 |
# Progress Tracking Tab
|
679 |
with gr.TabItem("π Student Progress"):
|
680 |
gr.Markdown("## View Student Progress")
|
|
|
133 |
|
134 |
return error_spans
|
135 |
|
136 |
+
def _get_error_spans_detailed(self, text):
|
137 |
+
"""Extract error spans with detailed second_level_tag categories"""
|
138 |
+
ged_tags_str, tokens, predictions = self._get_ged_predictions(text)
|
139 |
+
|
140 |
+
error_spans = []
|
141 |
+
error_types = []
|
142 |
+
clean_tokens = []
|
143 |
+
|
144 |
+
# Correct id2label mapping
|
145 |
+
id2label = {
|
146 |
+
0: "correct",
|
147 |
+
1: "ORTH",
|
148 |
+
2: "FORM",
|
149 |
+
3: "MORPH",
|
150 |
+
4: "DET",
|
151 |
+
5: "POS",
|
152 |
+
6: "VERB",
|
153 |
+
7: "NUM",
|
154 |
+
8: "WORD",
|
155 |
+
9: "PUNCT",
|
156 |
+
10: "RED",
|
157 |
+
11: "MULTIWORD",
|
158 |
+
12: "SPELL"
|
159 |
+
}
|
160 |
+
|
161 |
+
for token, pred in zip(tokens, predictions):
|
162 |
+
if token.startswith("##") or token in ["[CLS]", "[SEP]", "[PAD]"]:
|
163 |
+
continue
|
164 |
+
clean_tokens.append(token)
|
165 |
+
|
166 |
+
if pred != 0: # 0 is correct, others are various error types
|
167 |
+
error_type = id2label.get(pred, "OTHER")
|
168 |
+
error_types.append(error_type)
|
169 |
+
|
170 |
+
error_spans.append({
|
171 |
+
"token": token,
|
172 |
+
"type": error_type,
|
173 |
+
"position": len(clean_tokens) - 1
|
174 |
+
})
|
175 |
+
|
176 |
+
return error_spans, list(set(error_types))
|
177 |
+
|
178 |
def _preprocess_inputs(self, text, max_length=128):
|
179 |
"""Preprocess input text exactly as during training"""
|
180 |
# Get GED predictions
|
|
|
284 |
"""Enhanced analysis method for Gradio integration"""
|
285 |
if not text.strip():
|
286 |
return "Model not available or empty text", ""
|
287 |
+
|
288 |
try:
|
289 |
# Get corrected text
|
290 |
corrected_text = self.correct_text(text)
|
291 |
+
|
292 |
+
# Get error spans (use the original method for display)
|
293 |
error_spans = self._get_error_spans(text)
|
294 |
+
|
295 |
# Generate HTML output
|
296 |
html_output = self.generate_html_analysis(text, corrected_text, error_spans)
|
297 |
+
|
298 |
return corrected_text, html_output
|
299 |
+
|
300 |
except Exception as e:
|
301 |
return f"Error during analysis: {str(e)}", ""
|
302 |
|
|
|
359 |
</div>
|
360 |
"""
|
361 |
return html
|
362 |
+
|
363 |
+
def clear_and_reload_database():
|
364 |
+
"""Clear and reload the sentence database"""
|
365 |
+
conn = sqlite3.connect('language_app.db')
|
366 |
+
c = conn.cursor()
|
367 |
+
|
368 |
+
# Clear existing data
|
369 |
+
c.execute("DELETE FROM sentence_database")
|
370 |
+
conn.commit()
|
371 |
+
print("Cleared existing sentence database")
|
372 |
+
|
373 |
+
conn.close()
|
374 |
+
|
375 |
+
# Reload
|
376 |
+
load_sentence_database()
|
377 |
|
378 |
# Initialize SQLite database for storing submissions and exercises
|
379 |
def init_database():
|
|
|
432 |
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
433 |
)''')
|
434 |
|
435 |
+
# Sentence database table - ADD THIS
|
436 |
+
c.execute('''CREATE TABLE IF NOT EXISTS sentence_database (
|
437 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
438 |
+
text TEXT NOT NULL,
|
439 |
+
tags TEXT NOT NULL,
|
440 |
+
error_types TEXT NOT NULL
|
441 |
+
)''')
|
442 |
+
|
443 |
conn.commit()
|
444 |
conn.close()
|
445 |
|
446 |
+
|
447 |
+
def load_sentence_database(jsonl_file_path='sentencewise_full.jsonl'):
|
448 |
+
"""Load sentence database from JSONL file"""
|
449 |
+
print(f"Debug: Attempting to load from: {jsonl_file_path}")
|
450 |
+
print(f"Debug: Current working directory: {os.getcwd()}")
|
451 |
+
print(f"Debug: File exists: {os.path.exists(jsonl_file_path)}")
|
452 |
+
|
453 |
+
conn = sqlite3.connect('language_app.db')
|
454 |
+
c = conn.cursor()
|
455 |
+
|
456 |
+
# Create sentence database table
|
457 |
+
c.execute('''CREATE TABLE IF NOT EXISTS sentence_database (
|
458 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
459 |
+
text TEXT NOT NULL,
|
460 |
+
tags TEXT NOT NULL,
|
461 |
+
error_types TEXT NOT NULL
|
462 |
+
)''')
|
463 |
+
|
464 |
+
# Check if data already loaded
|
465 |
+
c.execute("SELECT COUNT(*) FROM sentence_database")
|
466 |
+
current_count = c.fetchone()[0]
|
467 |
+
if current_count > 0:
|
468 |
+
print(f"Sentence database already loaded with {current_count} sentences")
|
469 |
+
conn.close()
|
470 |
+
return
|
471 |
+
|
472 |
+
# Load JSONL file
|
473 |
+
try:
|
474 |
+
print(f"Debug: Opening file {jsonl_file_path}")
|
475 |
+
with open(jsonl_file_path, 'r', encoding='utf-8') as f:
|
476 |
+
lines_processed = 0
|
477 |
+
for line_num, line in enumerate(f, 1):
|
478 |
+
try:
|
479 |
+
line = line.strip()
|
480 |
+
if not line: # Skip empty lines
|
481 |
+
continue
|
482 |
+
|
483 |
+
data = json.loads(line)
|
484 |
+
text = data.get('text', '')
|
485 |
+
tags = data.get('tags', [])
|
486 |
+
|
487 |
+
if not text or not tags:
|
488 |
+
print(f"Debug: Skipping line {line_num} - missing text or tags")
|
489 |
+
continue
|
490 |
+
|
491 |
+
# Extract second_level_tag error types
|
492 |
+
error_types = []
|
493 |
+
for tag in tags:
|
494 |
+
second_level = tag.get('second_level_tag', '')
|
495 |
+
if second_level:
|
496 |
+
error_types.append(second_level)
|
497 |
+
|
498 |
+
error_types = list(set(error_types)) # Remove duplicates
|
499 |
+
|
500 |
+
# Debug: Print first few entries
|
501 |
+
if line_num <= 3:
|
502 |
+
print(f"Debug line {line_num}: text='{text[:50]}...', error_types={error_types}")
|
503 |
+
print(f"Debug: Raw tags for line {line_num}: {tags}")
|
504 |
+
|
505 |
+
if error_types: # Only insert if we have error types
|
506 |
+
c.execute("""INSERT INTO sentence_database (text, tags, error_types)
|
507 |
+
VALUES (?, ?, ?)""",
|
508 |
+
(text, json.dumps(tags), json.dumps(error_types)))
|
509 |
+
lines_processed += 1
|
510 |
+
|
511 |
+
if line_num % 1000 == 0:
|
512 |
+
print(f"Processed {line_num} lines, inserted {lines_processed} sentences...")
|
513 |
+
|
514 |
+
except json.JSONDecodeError as e:
|
515 |
+
print(f"JSON decode error on line {line_num}: {e}")
|
516 |
+
print(f"Line content: {line[:100]}...")
|
517 |
+
continue
|
518 |
+
except Exception as e:
|
519 |
+
print(f"Error processing line {line_num}: {e}")
|
520 |
+
continue
|
521 |
+
|
522 |
+
conn.commit()
|
523 |
+
print(f"Successfully loaded sentence database with {lines_processed} sentences from {line_num} total lines")
|
524 |
+
|
525 |
+
except FileNotFoundError:
|
526 |
+
print(f"Error: {jsonl_file_path} not found in {os.getcwd()}")
|
527 |
+
print("Available files:")
|
528 |
+
try:
|
529 |
+
files = os.listdir('.')
|
530 |
+
for f in files:
|
531 |
+
if f.endswith('.jsonl') or f.endswith('.json'):
|
532 |
+
print(f" - {f}")
|
533 |
+
except:
|
534 |
+
print(" Could not list files")
|
535 |
+
except Exception as e:
|
536 |
+
print(f"Error loading sentence database: {e}")
|
537 |
+
|
538 |
+
conn.close()
|
539 |
+
|
540 |
+
def find_similar_sentences(error_types, limit=5):
|
541 |
+
"""Find sentences with similar error types from database"""
|
542 |
+
if not error_types:
|
543 |
+
return []
|
544 |
+
|
545 |
+
conn = sqlite3.connect('language_app.db')
|
546 |
+
c = conn.cursor()
|
547 |
+
|
548 |
+
# Build query to find sentences with matching error types
|
549 |
+
similar_sentences = []
|
550 |
+
|
551 |
+
for error_type in error_types:
|
552 |
+
c.execute("""SELECT text, tags FROM sentence_database
|
553 |
+
WHERE error_types LIKE ?
|
554 |
+
ORDER BY RANDOM()
|
555 |
+
LIMIT ?""", (f'%"{error_type}"%', limit))
|
556 |
+
|
557 |
+
results = c.fetchall()
|
558 |
+
for text, tags_json in results:
|
559 |
+
similar_sentences.append({
|
560 |
+
'text': text,
|
561 |
+
'tags': json.loads(tags_json)
|
562 |
+
})
|
563 |
+
|
564 |
+
conn.close()
|
565 |
+
|
566 |
+
# Remove duplicates and limit to requested number
|
567 |
+
seen_texts = set()
|
568 |
+
unique_sentences = []
|
569 |
+
for sentence in similar_sentences:
|
570 |
+
if sentence['text'] not in seen_texts:
|
571 |
+
seen_texts.add(sentence['text'])
|
572 |
+
unique_sentences.append(sentence)
|
573 |
+
if len(unique_sentences) >= limit:
|
574 |
+
break
|
575 |
+
|
576 |
+
return unique_sentences
|
577 |
+
|
578 |
+
|
579 |
# Initialize database and components
|
580 |
init_database()
|
581 |
+
print("Clearing and loading sentence database...")
|
582 |
+
clear_and_reload_database()
|
583 |
print("Initializing enhanced grammar checker...")
|
584 |
grammar_checker = HuggingFaceT5GEDInference()
|
585 |
print("Grammar checker initialized successfully!")
|
|
|
624 |
|
625 |
return corrected_text, html_analysis
|
626 |
|
627 |
+
|
628 |
def create_exercise_from_text(text, exercise_title="Grammar Exercise"):
|
629 |
+
"""Create an exercise from text with errors using sentence database"""
|
630 |
if not text.strip():
|
631 |
return "Please enter text to create an exercise.", ""
|
632 |
|
633 |
+
# Analyze text to extract error types
|
634 |
sentences = nltk.sent_tokenize(text)
|
635 |
exercise_sentences = []
|
636 |
+
all_error_types = []
|
637 |
|
638 |
for sentence in sentences:
|
639 |
+
# Get detailed error analysis
|
640 |
+
error_spans, error_types = grammar_checker._get_error_spans_detailed(sentence)
|
641 |
+
|
642 |
+
if error_types: # Has errors
|
643 |
+
corrected, _ = grammar_checker.analyze_text(sentence)
|
644 |
exercise_sentences.append({
|
645 |
"original": sentence.strip(),
|
646 |
+
"corrected": corrected.strip(),
|
647 |
+
"error_types": error_types
|
648 |
})
|
649 |
+
all_error_types.extend(error_types)
|
650 |
|
651 |
if not exercise_sentences:
|
652 |
return "No errors found in the text. Cannot create exercise.", ""
|
653 |
|
654 |
+
# Find similar sentences from database
|
655 |
+
unique_error_types = list(set(all_error_types))
|
656 |
+
similar_sentences = find_similar_sentences(unique_error_types, limit=5)
|
657 |
+
|
658 |
+
# Combine original sentences with similar ones from database
|
659 |
+
all_exercise_sentences = exercise_sentences.copy()
|
660 |
+
|
661 |
+
for similar in similar_sentences:
|
662 |
+
# Get corrected version of similar sentence
|
663 |
+
corrected, _ = grammar_checker.analyze_text(similar['text'])
|
664 |
+
all_exercise_sentences.append({
|
665 |
+
"original": similar['text'],
|
666 |
+
"corrected": corrected,
|
667 |
+
"error_types": [tag.get('second_level_tag', '') for tag in similar['tags']]
|
668 |
+
})
|
669 |
+
|
670 |
# Store exercise in database
|
671 |
conn = sqlite3.connect('language_app.db')
|
672 |
c = conn.cursor()
|
|
|
675 |
VALUES (?, ?, ?)""",
|
676 |
(exercise_title,
|
677 |
"Correct the grammatical errors in the following sentences:",
|
678 |
+
json.dumps(all_exercise_sentences)))
|
679 |
|
680 |
exercise_id = c.lastrowid
|
681 |
conn.commit()
|
|
|
687 |
<h3>{exercise_title}</h3>
|
688 |
<p><strong>Exercise ID: {exercise_id}</strong></p>
|
689 |
<p><strong>Instructions:</strong> Correct the grammatical errors in the following sentences:</p>
|
690 |
+
<p><em>Error types found: {', '.join(unique_error_types)}</em></p>
|
691 |
<ol>
|
692 |
"""
|
693 |
|
694 |
+
for i, sentence_data in enumerate(all_exercise_sentences, 1):
|
695 |
+
error_info = f" (Error types: {', '.join(sentence_data.get('error_types', []))})" if sentence_data.get('error_types') else ""
|
696 |
+
exercise_html += f"<li style='margin: 10px 0; padding: 10px; background-color: #f8f9fa; border-radius: 4px;'>{sentence_data['original']}{error_info}</li>"
|
697 |
|
698 |
exercise_html += "</ol></div>"
|
699 |
|
700 |
+
return f"Exercise created with {len(all_exercise_sentences)} sentences ({len(exercise_sentences)} original + {len(similar_sentences)} from database)! Exercise ID: {exercise_id}", exercise_html
|
701 |
+
|
702 |
|
703 |
def attempt_exercise(exercise_id, student_responses, student_name):
|
704 |
"""Submit exercise attempt and get score using enhanced analysis"""
|
|
|
730 |
|
731 |
# Calculate score using enhanced analysis
|
732 |
correct_count = 0
|
733 |
+
detailed_results = []
|
734 |
|
735 |
for i, (sentence_data, response) in enumerate(zip(exercise_sentences, responses), 1):
|
736 |
+
original = sentence_data['original']
|
737 |
+
expected = sentence_data['corrected']
|
738 |
|
739 |
# Use the model to check if the response is correct
|
740 |
+
response_corrected, response_analysis = grammar_checker.analyze_text(response)
|
741 |
is_correct = response_corrected.strip() == response.strip() # No further corrections needed
|
742 |
|
743 |
if is_correct:
|
744 |
correct_count += 1
|
745 |
+
|
746 |
+
detailed_results.append({
|
747 |
+
'sentence_num': i,
|
748 |
+
'original': original,
|
749 |
+
'student_response': response,
|
750 |
+
'expected': expected,
|
751 |
+
'model_correction': response_corrected,
|
752 |
+
'is_correct': is_correct,
|
753 |
+
'analysis_html': response_analysis
|
754 |
+
})
|
755 |
|
756 |
score = (correct_count / len(exercise_sentences)) * 100
|
757 |
|
|
|
759 |
attempt_data = {
|
760 |
"responses": responses,
|
761 |
"score": score,
|
762 |
+
"detailed_results": detailed_results
|
763 |
}
|
764 |
|
765 |
c.execute("""INSERT INTO exercise_attempts (exercise_id, student_name, responses, score)
|
|
|
769 |
conn.commit()
|
770 |
conn.close()
|
771 |
|
772 |
+
# Create beautiful HTML results
|
773 |
+
score_color = "#28a745" if score >= 70 else "#ffc107" if score >= 50 else "#dc3545"
|
774 |
+
|
775 |
feedback_html = f"""
|
776 |
+
<div style='font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto;'>
|
777 |
+
<!-- Header Section -->
|
778 |
+
<div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px 10px 0 0; text-align: center;'>
|
779 |
+
<h2 style='margin: 0; font-size: 28px;'>π Exercise Results</h2>
|
780 |
+
<div style='margin-top: 15px; font-size: 48px; font-weight: bold; color: {score_color};'>{score:.1f}%</div>
|
781 |
+
<p style='margin: 10px 0 0 0; font-size: 18px; opacity: 0.9;'>{correct_count} out of {len(exercise_sentences)} sentences correct</p>
|
782 |
+
</div>
|
783 |
+
|
784 |
+
<!-- Performance Badge -->
|
785 |
+
<div style='background-color: #f8f9fa; padding: 20px; text-align: center; border-left: 1px solid #ddd; border-right: 1px solid #ddd;'>
|
786 |
+
"""
|
787 |
+
|
788 |
+
if score >= 90:
|
789 |
+
feedback_html += """<span style='background-color: #28a745; color: white; padding: 8px 20px; border-radius: 20px; font-weight: bold;'>π Excellent Work!</span>"""
|
790 |
+
elif score >= 70:
|
791 |
+
feedback_html += """<span style='background-color: #17a2b8; color: white; padding: 8px 20px; border-radius: 20px; font-weight: bold;'>π Good Job!</span>"""
|
792 |
+
elif score >= 50:
|
793 |
+
feedback_html += """<span style='background-color: #ffc107; color: white; padding: 8px 20px; border-radius: 20px; font-weight: bold;'>π Keep Practicing!</span>"""
|
794 |
+
else:
|
795 |
+
feedback_html += """<span style='background-color: #dc3545; color: white; padding: 8px 20px; border-radius: 20px; font-weight: bold;'>πͺ Try Again!</span>"""
|
796 |
+
|
797 |
+
feedback_html += """
|
798 |
+
</div>
|
799 |
+
|
800 |
+
<!-- Detailed Results -->
|
801 |
+
<div style='background-color: white; border: 1px solid #ddd; border-radius: 0 0 10px 10px;'>
|
802 |
+
"""
|
803 |
+
|
804 |
+
for result in detailed_results:
|
805 |
+
# Determine colors and icons
|
806 |
+
if result['is_correct']:
|
807 |
+
border_color = "#28a745"
|
808 |
+
icon = "β
"
|
809 |
+
status_bg = "#d4edda"
|
810 |
+
status_text = "Correct!"
|
811 |
+
else:
|
812 |
+
border_color = "#dc3545"
|
813 |
+
icon = "β"
|
814 |
+
status_bg = "#f8d7da"
|
815 |
+
status_text = "Needs Improvement"
|
816 |
+
|
817 |
+
feedback_html += f"""
|
818 |
+
<div style='border-left: 4px solid {border_color}; margin: 20px; padding: 20px; background-color: #fafafa; border-radius: 8px;'>
|
819 |
+
<div style='display: flex; align-items: center; margin-bottom: 15px;'>
|
820 |
+
<span style='font-size: 24px; margin-right: 10px;'>{icon}</span>
|
821 |
+
<h4 style='margin: 0; color: #333;'>Sentence {result['sentence_num']}</h4>
|
822 |
+
<span style='margin-left: auto; background-color: {status_bg}; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: bold;'>{status_text}</span>
|
823 |
+
</div>
|
824 |
+
|
825 |
+
<div style='margin-bottom: 15px;'>
|
826 |
+
<div style='margin-bottom: 10px;'>
|
827 |
+
<strong style='color: #6c757d;'>π Original:</strong>
|
828 |
+
<div style='background-color: #e9ecef; padding: 10px; border-radius: 6px; margin-top: 5px; font-style: italic;'>{result['original']}</div>
|
829 |
+
</div>
|
830 |
+
|
831 |
+
<div style='margin-bottom: 10px;'>
|
832 |
+
<strong style='color: #007bff;'>βοΈ Your Answer:</strong>
|
833 |
+
<div style='background-color: #e7f3ff; padding: 10px; border-radius: 6px; margin-top: 5px;'>{result['student_response']}</div>
|
834 |
+
</div>
|
835 |
+
"""
|
836 |
+
|
837 |
+
# Only show model analysis if there were errors in student's response
|
838 |
+
if not result['is_correct'] and result['analysis_html']:
|
839 |
+
feedback_html += f"""
|
840 |
+
<div style='margin-top: 15px; padding: 15px; background-color: #fff3cd; border-radius: 6px; border-left: 3px solid #ffc107;'>
|
841 |
+
<strong style='color: #856404;'>π Grammar Analysis of Your Response:</strong>
|
842 |
+
<div style='margin-top: 10px; font-size: 14px;'>
|
843 |
+
{result['analysis_html']}
|
844 |
+
</div>
|
845 |
+
</div>
|
846 |
+
"""
|
847 |
+
|
848 |
+
feedback_html += """
|
849 |
+
</div>
|
850 |
+
</div>
|
851 |
+
"""
|
852 |
+
|
853 |
+
feedback_html += """
|
854 |
+
</div>
|
855 |
+
|
856 |
+
<!-- Footer -->
|
857 |
+
<div style='text-align: center; margin-top: 30px; color: #6c757d; font-size: 14px;'>
|
858 |
+
<p>π‘ <strong>Tip:</strong> Review the grammar analysis above to understand common error patterns and improve your writing!</p>
|
859 |
</div>
|
860 |
</div>
|
861 |
"""
|
862 |
|
863 |
return f"Score: {score:.1f}%", feedback_html
|
864 |
|
865 |
+
|
866 |
+
def preview_exercise(exercise_id):
|
867 |
+
"""Preview an exercise before attempting it"""
|
868 |
+
if not exercise_id.strip():
|
869 |
+
return "Please enter an exercise ID.", ""
|
870 |
+
|
871 |
+
try:
|
872 |
+
exercise_id = int(exercise_id)
|
873 |
+
except:
|
874 |
+
return "Please enter a valid exercise ID.", ""
|
875 |
+
|
876 |
+
# Get exercise from database
|
877 |
+
conn = sqlite3.connect('language_app.db')
|
878 |
+
c = conn.cursor()
|
879 |
+
|
880 |
+
c.execute("SELECT title, instructions, sentences FROM exercises WHERE id = ?", (exercise_id,))
|
881 |
+
result = c.fetchone()
|
882 |
+
|
883 |
+
if not result:
|
884 |
+
return "Exercise not found.", ""
|
885 |
+
|
886 |
+
title, instructions, sentences_json = result
|
887 |
+
exercise_sentences = json.loads(sentences_json)
|
888 |
+
|
889 |
+
conn.close()
|
890 |
+
|
891 |
+
# Create preview HTML
|
892 |
+
preview_html = f"""
|
893 |
+
<div style='font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto;'>
|
894 |
+
<!-- Header -->
|
895 |
+
<div style='background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; padding: 25px; border-radius: 10px 10px 0 0; text-align: center;'>
|
896 |
+
<h2 style='margin: 0; font-size: 24px;'>π {title}</h2>
|
897 |
+
<p style='margin: 10px 0 0 0; font-size: 16px; opacity: 0.9;'>Exercise ID: {exercise_id}</p>
|
898 |
+
</div>
|
899 |
+
|
900 |
+
<!-- Instructions -->
|
901 |
+
<div style='background-color: #e8f5e9; padding: 20px; border-left: 1px solid #ddd; border-right: 1px solid #ddd;'>
|
902 |
+
<h3 style='margin: 0 0 10px 0; color: #2e7d32;'>π Instructions:</h3>
|
903 |
+
<p style='margin: 0; font-size: 16px; line-height: 1.5;'>{instructions}</p>
|
904 |
+
<p style='margin: 10px 0 0 0; font-size: 14px; color: #666; font-style: italic;'>
|
905 |
+
π‘ Tip: Read each sentence carefully and identify grammatical errors before writing your corrections.
|
906 |
+
</p>
|
907 |
+
</div>
|
908 |
+
|
909 |
+
<!-- Sentences -->
|
910 |
+
<div style='background-color: white; border: 1px solid #ddd; border-radius: 0 0 10px 10px; padding: 20px;'>
|
911 |
+
<h3 style='margin: 0 0 20px 0; color: #333;'>π Sentences to Correct ({len(exercise_sentences)} total):</h3>
|
912 |
+
<ol style='padding-left: 20px;'>
|
913 |
+
"""
|
914 |
+
|
915 |
+
for i, sentence_data in enumerate(exercise_sentences, 1):
|
916 |
+
original = sentence_data['original']
|
917 |
+
error_types = sentence_data.get('error_types', [])
|
918 |
+
|
919 |
+
# Add error type hints if available
|
920 |
+
error_hint = ""
|
921 |
+
if error_types:
|
922 |
+
error_hint = f"<br><small style='color: #666; font-style: italic;'>π‘ Focus on: {', '.join(error_types)}</small>"
|
923 |
+
|
924 |
+
preview_html += f"""
|
925 |
+
<li style='margin: 15px 0; padding: 15px; background-color: #f8f9fa; border-radius: 6px; border-left: 3px solid #4CAF50;'>
|
926 |
+
<div style='font-size: 16px; line-height: 1.5; margin-bottom: 5px;'>{original}</div>
|
927 |
+
{error_hint}
|
928 |
+
</li>
|
929 |
+
"""
|
930 |
+
|
931 |
+
preview_html += f"""
|
932 |
+
</ol>
|
933 |
+
|
934 |
+
<div style='margin-top: 30px; padding: 20px; background-color: #f0f8ff; border-radius: 8px; border: 1px solid #b3d9ff;'>
|
935 |
+
<h4 style='margin: 0 0 10px 0; color: #0066cc;'>π― How to Complete This Exercise:</h4>
|
936 |
+
<ol style='margin: 0; padding-left: 20px; color: #333;'>
|
937 |
+
<li>Read each sentence carefully</li>
|
938 |
+
<li>Identify grammatical errors (spelling, grammar, word choice, etc.)</li>
|
939 |
+
<li>Write your corrected version of each sentence</li>
|
940 |
+
<li>Enter all your answers in the text box below (one sentence per line)</li>
|
941 |
+
<li>Submit to get immediate feedback and scoring</li>
|
942 |
+
</ol>
|
943 |
+
</div>
|
944 |
+
</div>
|
945 |
+
</div>
|
946 |
+
"""
|
947 |
+
|
948 |
+
return f"Exercise '{title}' loaded successfully! {len(exercise_sentences)} sentences to correct.", preview_html
|
949 |
+
|
950 |
+
|
951 |
def get_student_progress(student_name):
|
952 |
"""Get student's submission and exercise history"""
|
953 |
if not student_name.strip():
|
|
|
1054 |
# Exercise Attempt Tab
|
1055 |
with gr.TabItem("βοΈ Exercise Practice"):
|
1056 |
gr.Markdown("## Practice Grammar Exercises")
|
|
|
1057 |
with gr.Row():
|
1058 |
with gr.Column():
|
1059 |
exercise_id_input = gr.Textbox(label="Exercise ID", placeholder="Enter exercise ID")
|
1060 |
+
|
1061 |
+
# Preview section
|
1062 |
+
with gr.Row():
|
1063 |
+
preview_btn = gr.Button("π Preview Exercise", variant="secondary")
|
1064 |
+
|
1065 |
+
preview_result = gr.Textbox(label="Preview Status", lines=1)
|
1066 |
+
preview_display = gr.HTML(label="Exercise Preview")
|
1067 |
+
|
1068 |
+
# Separator
|
1069 |
+
gr.Markdown("---")
|
1070 |
+
|
1071 |
+
# Attempt section
|
1072 |
+
gr.Markdown("### π Complete the Exercise")
|
1073 |
student_name_exercise = gr.Textbox(label="Your Name", placeholder="Enter your name")
|
1074 |
responses_input = gr.Textbox(
|
1075 |
label="Your Answers",
|
1076 |
+
lines=8,
|
1077 |
+
placeholder="After previewing the exercise above, enter your corrected sentences here (one per line)..."
|
1078 |
)
|
1079 |
+
submit_exercise_btn = gr.Button("β
Submit Answers", variant="primary")
|
1080 |
|
1081 |
with gr.Column():
|
1082 |
score_output = gr.Textbox(label="Your Score")
|
1083 |
feedback_output = gr.HTML(label="Detailed Feedback")
|
1084 |
|
1085 |
+
# Connect the buttons
|
1086 |
+
preview_btn.click(
|
1087 |
+
preview_exercise,
|
1088 |
+
inputs=[exercise_id_input],
|
1089 |
+
outputs=[preview_result, preview_display]
|
1090 |
+
)
|
1091 |
+
|
1092 |
submit_exercise_btn.click(
|
1093 |
attempt_exercise,
|
1094 |
inputs=[exercise_id_input, responses_input, student_name_exercise],
|
1095 |
outputs=[score_output, feedback_output]
|
1096 |
)
|
|
|
1097 |
# Progress Tracking Tab
|
1098 |
with gr.TabItem("π Student Progress"):
|
1099 |
gr.Markdown("## View Student Progress")
|