Spaces:
Sleeping
Sleeping
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")
|