Zlovoblachko commited on
Commit
62e8dda
Β·
1 Parent(s): 4b3ae07

databank added

Browse files
Files changed (1) hide show
  1. app.py +451 -32
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 enhanced analysis"""
429
  if not text.strip():
430
  return "Please enter text to create an exercise.", ""
431
 
432
- # Analyze text to find sentences with errors
433
  sentences = nltk.sent_tokenize(text)
434
  exercise_sentences = []
 
435
 
436
  for sentence in sentences:
437
- corrected, _ = grammar_checker.analyze_text(sentence)
438
- if sentence.strip() != corrected.strip(): # Has errors
 
 
 
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(exercise_sentences)))
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(exercise_sentences, 1):
471
- exercise_html += f"<li style='margin: 10px 0; padding: 10px; background-color: #f8f9fa; border-radius: 4px;'>{sentence_data['original']}</li>"
 
472
 
473
  exercise_html += "</ol></div>"
474
 
475
- return f"Exercise created with {len(exercise_sentences)} sentences! Exercise ID: {exercise_id}", exercise_html
 
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
- feedback = []
508
 
509
  for i, (sentence_data, response) in enumerate(zip(exercise_sentences, responses), 1):
510
- correct_answer = sentence_data['corrected']
 
511
 
512
  # Use the model to check if the response is correct
513
- response_corrected, _ = grammar_checker.analyze_text(response)
514
  is_correct = response_corrected.strip() == response.strip() # No further corrections needed
515
 
516
  if is_correct:
517
  correct_count += 1
518
- feedback.append(f"βœ… Sentence {i}: Excellent! No errors detected.")
519
- else:
520
- feedback.append(f"❌ Sentence {i}: Your answer: '{response}' | Suggested improvement: '{response_corrected}' | Expected: '{correct_answer}'")
 
 
 
 
 
 
 
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
- "feedback": feedback
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; padding: 20px; border: 1px solid #ddd; border-radius: 8px;'>
540
- <h3>Exercise Results</h3>
541
- <p><strong>Score: {score:.1f}%</strong> ({correct_count}/{len(exercise_sentences)} correct)</p>
542
- <div style='margin-top: 15px;'>
543
- {'<br>'.join(feedback)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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=6,
664
- placeholder="Enter your corrected sentences (one per line)..."
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")