youssefreda9 commited on
Commit
2702d02
·
1 Parent(s): 0898117

fix: contextual logging, word merge overlap resolver, and responsive toolbar/footer UI

Browse files
src/css/components.css CHANGED
@@ -413,11 +413,13 @@
413
  /* ── Formatting Toolbar ── */
414
  .format-toolbar {
415
  display: flex;
416
- flex-wrap: wrap;
417
  align-items: center;
418
  justify-content: center;
419
  gap: 3px;
420
  padding: 6px 10px;
 
 
421
  border-bottom: 1px solid var(--color-border);
422
  background: var(--color-surface);
423
  overflow: visible;
@@ -426,9 +428,13 @@
426
  flex-shrink: 0;
427
  }
428
 
 
 
 
 
429
  .fmt-group {
430
  display: flex;
431
- flex-wrap: wrap;
432
  align-items: center;
433
  gap: 1px;
434
  background: var(--color-border);
@@ -687,18 +693,23 @@
687
 
688
  .editor-footer {
689
  display: flex;
690
- flex-wrap: wrap;
691
  align-items: center;
692
  justify-content: space-between;
693
  gap: var(--spacing-md);
694
  padding: var(--spacing-md);
695
  border-top: 1px solid var(--color-border);
696
  background: var(--color-surface);
 
 
 
 
 
697
  }
698
 
699
  .editor-stats {
700
  display: flex;
701
- flex-wrap: wrap;
702
  gap: var(--spacing-md);
703
  align-items: center;
704
  }
@@ -724,7 +735,7 @@
724
 
725
  .editor-actions {
726
  display: flex;
727
- flex-wrap: wrap;
728
  gap: var(--spacing-sm);
729
  }
730
 
@@ -737,6 +748,7 @@
737
  display: inline-flex;
738
  align-items: center;
739
  justify-content: center;
 
740
  }
741
 
742
  /* ── Buttons ── */
 
413
  /* ── Formatting Toolbar ── */
414
  .format-toolbar {
415
  display: flex;
416
+ flex-wrap: nowrap;
417
  align-items: center;
418
  justify-content: center;
419
  gap: 3px;
420
  padding: 6px 10px;
421
+ overflow-x: auto;
422
+ scrollbar-width: none; /* Hide scrollbar Firefox */
423
  border-bottom: 1px solid var(--color-border);
424
  background: var(--color-surface);
425
  overflow: visible;
 
428
  flex-shrink: 0;
429
  }
430
 
431
+ .format-toolbar::-webkit-scrollbar {
432
+ display: none;
433
+ }
434
+
435
  .fmt-group {
436
  display: flex;
437
+ flex-wrap: nowrap;
438
  align-items: center;
439
  gap: 1px;
440
  background: var(--color-border);
 
693
 
694
  .editor-footer {
695
  display: flex;
696
+ flex-wrap: nowrap;
697
  align-items: center;
698
  justify-content: space-between;
699
  gap: var(--spacing-md);
700
  padding: var(--spacing-md);
701
  border-top: 1px solid var(--color-border);
702
  background: var(--color-surface);
703
+ overflow-x: auto;
704
+ scrollbar-width: none;
705
+ }
706
+ .editor-footer::-webkit-scrollbar {
707
+ display: none;
708
  }
709
 
710
  .editor-stats {
711
  display: flex;
712
+ flex-wrap: nowrap;
713
  gap: var(--spacing-md);
714
  align-items: center;
715
  }
 
735
 
736
  .editor-actions {
737
  display: flex;
738
+ flex-wrap: nowrap;
739
  gap: var(--spacing-sm);
740
  }
741
 
 
748
  display: inline-flex;
749
  align-items: center;
750
  justify-content: center;
751
+ flex-shrink: 0;
752
  }
753
 
754
  /* ── Buttons ── */
src/nlp/correction_patch.py CHANGED
@@ -16,7 +16,7 @@ from dataclasses import dataclass, field
16
 
17
  logger = logging.getLogger(__name__)
18
 
19
- PRIORITY = {'autocomplete': 0, 'spelling': 1, 'punctuation': 2, 'grammar': 3}
20
 
21
 
22
  @dataclass
@@ -131,6 +131,7 @@ class PatchSet:
131
  prev_correction = claimed_patch.replacement
132
 
133
  # Check if punctuation is just appending trailing punctuation
 
134
  if (len(punc_correction) > len(prev_correction)
135
  and punc_correction.startswith(prev_correction)
136
  and all(c in _PUNCT_CHARS for c in punc_correction[len(prev_correction):])):
@@ -143,6 +144,23 @@ class PatchSet:
143
  has_substantial_overlap = True
144
  break
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  # Check if punctuation is just prepending leading punctuation
147
  if (len(punc_correction) > len(prev_correction)
148
  and punc_correction.endswith(prev_correction)
 
16
 
17
  logger = logging.getLogger(__name__)
18
 
19
+ PRIORITY = {'autocomplete': 0, 'punctuation': 1, 'spelling': 2, 'grammar': 3}
20
 
21
 
22
  @dataclass
 
131
  prev_correction = claimed_patch.replacement
132
 
133
  # Check if punctuation is just appending trailing punctuation
134
+ # Scenario A: Exact match merge (prev_correction is prefix)
135
  if (len(punc_correction) > len(prev_correction)
136
  and punc_correction.startswith(prev_correction)
137
  and all(c in _PUNCT_CHARS for c in punc_correction[len(prev_correction):])):
 
144
  has_substantial_overlap = True
145
  break
146
 
147
+ # Scenario B: Punctuation just adds punct to its own original text
148
+ # (e.g. original='المدرسة', replacement='المدرسة.', but prev_correction is a split like 'في المدرسة')
149
+ if (len(punc_correction) > len(patch.original)
150
+ and punc_correction.startswith(patch.original)
151
+ and all(c in _PUNCT_CHARS for c in punc_correction[len(patch.original):])):
152
+ added_punct = punc_correction[len(patch.original):]
153
+ # Only append if it doesn't already end with that punct
154
+ if not claimed_patch.replacement.endswith(added_punct):
155
+ claimed_patch.replacement += added_punct
156
+ logger.info(
157
+ f"[OVERLAP] Appended trailing punctuation into {claimed_stage} "
158
+ f"[{cs}:{ce}]: '{claimed_patch.original}' → "
159
+ f"'{claimed_patch.replacement}'"
160
+ )
161
+ has_substantial_overlap = True
162
+ break
163
+
164
  # Check if punctuation is just prepending leading punctuation
165
  if (len(punc_correction) > len(prev_correction)
166
  and punc_correction.endswith(prev_correction)
src/nlp/spelling/araspell_rules.py CHANGED
@@ -1175,14 +1175,23 @@ class ArabicSpellChecker:
1175
  self.use_contextual = use_contextual
1176
  if use_contextual:
1177
  try:
 
 
1178
  self.contextual = ContextualCorrector()
1179
- logger.info("Contextual correction enabled")
 
 
 
1180
  except Exception as e:
1181
- logger.warning(f"Contextual correction disabled: {e}")
 
 
 
1182
  self.contextual = None
1183
  self.use_contextual = False
1184
  else:
1185
  self.contextual = None
 
1186
 
1187
  def _fix_repeated_end_chars(self, text: str) -> str:
1188
  text = re.sub(r'([ا-ي])\1+\b', r'\1', text)
 
1175
  self.use_contextual = use_contextual
1176
  if use_contextual:
1177
  try:
1178
+ logger.info("=" * 60)
1179
+ logger.info("[MLM/CONTEXTUAL] Loading AraBERT MLM model...")
1180
  self.contextual = ContextualCorrector()
1181
+ logger.info("[MLM/CONTEXTUAL] LOADED SUCCESSFULLY")
1182
+ logger.info(f"[MLM/CONTEXTUAL] Device: {self.contextual.device}")
1183
+ logger.info(f"[MLM/CONTEXTUAL] Vocab size: {len(self.contextual.vocab)}")
1184
+ logger.info("=" * 60)
1185
  except Exception as e:
1186
+ logger.warning("=" * 60)
1187
+ logger.warning(f"[MLM/CONTEXTUAL] ❌ FAILED TO LOAD: {e}")
1188
+ logger.warning("[MLM/CONTEXTUAL] Spelling will work without contextual validation")
1189
+ logger.warning("=" * 60)
1190
  self.contextual = None
1191
  self.use_contextual = False
1192
  else:
1193
  self.contextual = None
1194
+ logger.info("[MLM/CONTEXTUAL] Disabled by configuration (use_contextual=False)")
1195
 
1196
  def _fix_repeated_end_chars(self, text: str) -> str:
1197
  text = re.sub(r'([ا-ي])\1+\b', r'\1', text)
tests/test_bug_fixes.py CHANGED
@@ -207,13 +207,13 @@ class TestGrammarSanityCheck(unittest.TestCase):
207
  """
208
 
209
  def test_sanity_check_pattern_exists(self):
210
- """app.py grammar stage must contain the IV/OOV sanity check."""
211
- app_path = os.path.join(os.path.dirname(__file__), '..', 'src', 'app.py')
212
- with open(app_path, 'r', encoding='utf-8') as f:
213
  content = f.read()
214
  # Check for the Phase 4 guard comment and logic
215
  self.assertIn('Phase 4 (BUG-033/E10)', content,
216
- "Phase 4 grammar sanity check not found in app.py")
217
  self.assertIn('Rejected corruption', content,
218
  "Grammar corruption rejection log not found")
219
 
@@ -433,7 +433,7 @@ class TestLongInputPattern(unittest.TestCase):
433
  app_path = os.path.join(os.path.dirname(__file__), '..', 'src', 'app.py')
434
  with open(app_path, 'r', encoding='utf-8') as f:
435
  content = f.read()
436
- self.assertIn('text_len <= 300', content)
437
  self.assertIn('skipping AraSpell', content)
438
 
439
 
 
207
  """
208
 
209
  def test_sanity_check_pattern_exists(self):
210
+ """grammar_rules.py must contain the IV/OOV sanity check."""
211
+ target_path = os.path.join(os.path.dirname(__file__), '..', 'src', 'nlp', 'grammar', 'grammar_rules.py')
212
+ with open(target_path, 'r', encoding='utf-8') as f:
213
  content = f.read()
214
  # Check for the Phase 4 guard comment and logic
215
  self.assertIn('Phase 4 (BUG-033/E10)', content,
216
+ "Phase 4 grammar sanity check not found in grammar_rules.py")
217
  self.assertIn('Rejected corruption', content,
218
  "Grammar corruption rejection log not found")
219
 
 
433
  app_path = os.path.join(os.path.dirname(__file__), '..', 'src', 'app.py')
434
  with open(app_path, 'r', encoding='utf-8') as f:
435
  content = f.read()
436
+ self.assertIn('text_len <= 1000', content)
437
  self.assertIn('skipping AraSpell', content)
438
 
439
 
tests/test_recent_fixes.py CHANGED
@@ -250,13 +250,13 @@ ps2.add(CorrectionPatch(
250
  priority=PRIORITY['punctuation'], confidence=0.8
251
  ))
252
  resolved2 = ps2.resolve_overlaps()
253
- test("Case 2: punc dropped (non-matching)", len(resolved2) == 1, f"got {len(resolved2)}")
254
  if resolved2:
255
- test("Kept grammar unchanged 'ذهبن'",
256
- resolved2[0].replacement == 'ذهبن',
257
  f"got '{resolved2[0].replacement}'")
258
 
259
- # Case 3: spelling + punctuation still coexist (Phase 14)
260
  ps3 = PatchSet()
261
  ps3.add(CorrectionPatch(
262
  stage='spelling', start_original=0, end_original=5,
@@ -271,8 +271,12 @@ ps3.add(CorrectionPatch(
271
  priority=PRIORITY['punctuation'], confidence=0.8
272
  ))
273
  resolved3 = ps3.resolve_overlaps()
274
- test("Case 3: spelling+punc coexist (2 patches)",
275
- len(resolved3) == 2, f"got {len(resolved3)}")
 
 
 
 
276
 
277
 
278
  # ══════════════════════════════════════════════════════════════
 
250
  priority=PRIORITY['punctuation'], confidence=0.8
251
  ))
252
  resolved2 = ps2.resolve_overlaps()
253
+ test("Case 2: punc merged", len(resolved2) == 1, f"got {len(resolved2)}")
254
  if resolved2:
255
+ test("Appended punc to grammar 'ذهبن.'",
256
+ resolved2[0].replacement == 'ذهبن.',
257
  f"got '{resolved2[0].replacement}'")
258
 
259
+ # Case 3: spelling + punctuation merge (Phase 14 fix)
260
  ps3 = PatchSet()
261
  ps3.add(CorrectionPatch(
262
  stage='spelling', start_original=0, end_original=5,
 
271
  priority=PRIORITY['punctuation'], confidence=0.8
272
  ))
273
  resolved3 = ps3.resolve_overlaps()
274
+ test("Case 3: spelling+punc merged (1 patch)",
275
+ len(resolved3) == 1, f"got {len(resolved3)}")
276
+ if resolved3:
277
+ test("Appended punc to spelling 'برغم.'",
278
+ resolved3[0].replacement == 'برغم.',
279
+ f"got '{resolved3[0].replacement}'")
280
 
281
 
282
  # ══════════════════════════════════════════════════════════════