youssefreda9 commited on
Commit
42357d1
·
1 Parent(s): c081da8

feat: enhance extension UI and improve Smart Colon Guard

Browse files

- Added visual divider lines to Chrome extension logos (Popup, Sidepanel, Inline)
- Synchronized light/dark theme toggle across all extension views via chrome.storage.local
- Fixed sidepanel theme variables to ensure responsive toggle
- Re-ordered z-index for login overlay to ensure it covers the editor
- Improved punctuation rules with a Smart Colon Guard that checks 6 words back for speech cues and perfectly retains colons even for long subjects

LOGOS/icon128.png ADDED

Git LFS Details

  • SHA256: edf4e739bd7979282497a93e5eb79ba9ec15ec51ffb09288e710e27ef10af54f
  • Pointer size: 130 Bytes
  • Size of remote file: 14.3 kB
LOGOS/icon16.png ADDED

Git LFS Details

  • SHA256: 430d94175bb601012bcaa542e13012a7d49cccfb0f7e91e37b4e638962520650
  • Pointer size: 128 Bytes
  • Size of remote file: 845 Bytes
LOGOS/icon32.png ADDED

Git LFS Details

  • SHA256: 5dd959d57cd298801efd8e58f996f45b6913220538596b8918d4a8e4cbb98eac
  • Pointer size: 129 Bytes
  • Size of remote file: 2.2 kB
LOGOS/icon48.png ADDED

Git LFS Details

  • SHA256: ce4142d81117b1b950017f599e09ceddc0d89e517b74e796374b1ceb36555014
  • Pointer size: 129 Bytes
  • Size of remote file: 3.8 kB
extension/assets/icons/icon128.png CHANGED

Git LFS Details

  • SHA256: c5ee28b2d2e3fe100a925bf2d6412d6879a5f20a0a130260346cdab757811285
  • Pointer size: 130 Bytes
  • Size of remote file: 18.3 kB

Git LFS Details

  • SHA256: edf4e739bd7979282497a93e5eb79ba9ec15ec51ffb09288e710e27ef10af54f
  • Pointer size: 130 Bytes
  • Size of remote file: 14.3 kB
extension/assets/icons/icon16.png CHANGED

Git LFS Details

  • SHA256: 199d7a6107aed5a8af9dbf6457a87235c7cc2a908b04a68fd867918e824d4920
  • Pointer size: 128 Bytes
  • Size of remote file: 732 Bytes

Git LFS Details

  • SHA256: 430d94175bb601012bcaa542e13012a7d49cccfb0f7e91e37b4e638962520650
  • Pointer size: 128 Bytes
  • Size of remote file: 845 Bytes
extension/assets/icons/icon32.png CHANGED

Git LFS Details

  • SHA256: d615f4654fff91e034341ac86dfb3d8f582ff57180c191b4b112c51980a80c21
  • Pointer size: 129 Bytes
  • Size of remote file: 1.98 kB

Git LFS Details

  • SHA256: 5dd959d57cd298801efd8e58f996f45b6913220538596b8918d4a8e4cbb98eac
  • Pointer size: 129 Bytes
  • Size of remote file: 2.2 kB
extension/assets/icons/icon48.png CHANGED

Git LFS Details

  • SHA256: cfe7d9b88fb6f8e6e08d766b21cab6dd6c7519c6af35e1ab1050343064ee7d03
  • Pointer size: 129 Bytes
  • Size of remote file: 3.67 kB

Git LFS Details

  • SHA256: ce4142d81117b1b950017f599e09ceddc0d89e517b74e796374b1ceb36555014
  • Pointer size: 129 Bytes
  • Size of remote file: 3.8 kB
extension/content-inline.css CHANGED
@@ -286,3 +286,53 @@
286
  opacity: 0.85 !important;
287
  transition: none !important;
288
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  opacity: 0.85 !important;
287
  transition: none !important;
288
  }
289
+
290
+
291
+ /* ── Light Theme Overrides for Inline UI ── */
292
+ .bayan-il-fab[data-bayan-theme="light"] {
293
+ background: #ffffff !important;
294
+ border: 1px solid #e5e7eb !important;
295
+ }
296
+
297
+ .bayan-il-fab[data-bayan-theme="light"] svg path {
298
+ fill: #6366f1 !important;
299
+ }
300
+
301
+ .bayan-il-tooltip[data-bayan-theme="light"] {
302
+ background: #ffffff !important;
303
+ border: 1px solid #e5e7eb !important;
304
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(99, 102, 241, 0.1) !important;
305
+ }
306
+
307
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-header {
308
+ background: #f9fafb !important;
309
+ border-bottom: 1px solid #e5e7eb !important;
310
+ color: #4b5563 !important;
311
+ }
312
+
313
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-body {
314
+ background: #ffffff !important;
315
+ }
316
+
317
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-original {
318
+ color: #4b5563 !important;
319
+ }
320
+
321
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-correction {
322
+ color: #111827 !important;
323
+ }
324
+
325
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-actions {
326
+ border-top: 1px solid #e5e7eb !important;
327
+ background: #f9fafb !important;
328
+ }
329
+
330
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-ignore {
331
+ color: #4b5563 !important;
332
+ }
333
+
334
+ .bayan-il-tooltip[data-bayan-theme="light"] .bayan-il-tooltip-ignore:hover {
335
+ background: #e5e7eb !important;
336
+ color: #111827 !important;
337
+ }
338
+
extension/content-inline.js CHANGED
@@ -419,6 +419,7 @@
419
  const typeLabels = { spelling: 'إملائي', grammar: 'نحوي', punctuation: 'ترقيم' };
420
 
421
  tooltip = document.createElement('div');
 
422
  tooltip.className = 'bayan-il-tooltip';
423
  tooltip.dir = 'rtl';
424
 
@@ -566,6 +567,7 @@
566
  loadFabPosition();
567
 
568
  floatingBtn = document.createElement('div');
 
569
  floatingBtn.className = 'bayan-il-fab';
570
  safeHTML(floatingBtn, `
571
  <svg width="18" height="18" viewBox="0 0 100 100" fill="none">
@@ -1053,4 +1055,22 @@
1053
  // ── Log ──
1054
  const mode = IS_PROTECTED ? 'protected' : 'full';
1055
  console.log(`[Bayan] Inline engine v7.1 (mode: ${mode}, TT: ${ttPolicy ? 'yes' : 'no'})`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1056
  })();
 
419
  const typeLabels = { spelling: 'إملائي', grammar: 'نحوي', punctuation: 'ترقيم' };
420
 
421
  tooltip = document.createElement('div');
422
+ tooltip.setAttribute('data-bayan-theme', currentBayanTheme);
423
  tooltip.className = 'bayan-il-tooltip';
424
  tooltip.dir = 'rtl';
425
 
 
567
  loadFabPosition();
568
 
569
  floatingBtn = document.createElement('div');
570
+ floatingBtn.setAttribute('data-bayan-theme', currentBayanTheme);
571
  floatingBtn.className = 'bayan-il-fab';
572
  safeHTML(floatingBtn, `
573
  <svg width="18" height="18" viewBox="0 0 100 100" fill="none">
 
1055
  // ── Log ──
1056
  const mode = IS_PROTECTED ? 'protected' : 'full';
1057
  console.log(`[Bayan] Inline engine v7.1 (mode: ${mode}, TT: ${ttPolicy ? 'yes' : 'no'})`);
1058
+
1059
+
1060
+ // ── Theme Sync for Inline UI ──
1061
+ let currentBayanTheme = 'dark';
1062
+ chrome.storage.local.get(['theme'], (res) => {
1063
+ currentBayanTheme = res.theme || 'dark';
1064
+ if (typeof tooltip !== 'undefined' && tooltip) tooltip.setAttribute('data-bayan-theme', currentBayanTheme);
1065
+ if (typeof floatingBtn !== 'undefined' && floatingBtn) floatingBtn.setAttribute('data-bayan-theme', currentBayanTheme);
1066
+ });
1067
+
1068
+ chrome.storage.onChanged.addListener((changes, namespace) => {
1069
+ if (namespace === 'local' && changes.theme) {
1070
+ currentBayanTheme = changes.theme.newValue;
1071
+ if (typeof tooltip !== 'undefined' && tooltip) tooltip.setAttribute('data-bayan-theme', currentBayanTheme);
1072
+ if (typeof floatingBtn !== 'undefined' && floatingBtn) floatingBtn.setAttribute('data-bayan-theme', currentBayanTheme);
1073
+ }
1074
+ });
1075
+
1076
  })();
extension/popup.css CHANGED
@@ -767,3 +767,69 @@ body::-webkit-scrollbar-thumb {
767
  .is-hidden {
768
  display: none !important;
769
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
767
  .is-hidden {
768
  display: none !important;
769
  }
770
+
771
+
772
+ /* Light Theme Variables */
773
+ [data-theme="light"] {
774
+ --bayan-bg: #f9fafb;
775
+ --bayan-surface: #ffffff;
776
+ --bayan-surface-hover: #f3f4f6;
777
+ --bayan-surface-active: #e5e7eb;
778
+ --bayan-border: #e5e7eb;
779
+ --bayan-border-light: #d1d5db;
780
+ --bayan-text: #111827;
781
+ --bayan-text-secondary: #4b5563;
782
+ --bayan-text-muted: #9ca3af;
783
+ --bayan-success: #16a34a;
784
+ --bayan-warning: #d97706;
785
+ }
786
+
787
+ /* Theme Toggle Button Styles */
788
+ .theme-toggle-animated {
789
+ display: flex;
790
+ align-items: center;
791
+ justify-content: center;
792
+ width: 32px;
793
+ height: 32px;
794
+ border: none;
795
+ border-radius: 50%;
796
+ background: var(--bayan-surface-hover);
797
+ color: var(--bayan-text-secondary);
798
+ cursor: pointer;
799
+ transition: background 0.3s ease, transform 0.3s ease, color 0.3s ease;
800
+ position: relative;
801
+ overflow: hidden;
802
+ margin-right: 8px;
803
+ }
804
+
805
+ .theme-toggle-animated:hover {
806
+ background: var(--bayan-primary);
807
+ color: #fff;
808
+ transform: rotate(15deg);
809
+ }
810
+
811
+ .theme-toggle-animated svg {
812
+ transition: transform 0.4s ease, opacity 0.3s ease;
813
+ position: absolute;
814
+ }
815
+
816
+ [data-theme="dark"] .theme-icon-sun {
817
+ transform: rotate(90deg) scale(0);
818
+ opacity: 0;
819
+ }
820
+
821
+ [data-theme="dark"] .theme-icon-moon {
822
+ transform: rotate(0) scale(1);
823
+ opacity: 1;
824
+ }
825
+
826
+ [data-theme="light"] .theme-icon-moon {
827
+ transform: rotate(-90deg) scale(0);
828
+ opacity: 0;
829
+ }
830
+
831
+ [data-theme="light"] .theme-icon-sun {
832
+ transform: rotate(0) scale(1);
833
+ opacity: 1;
834
+ }
835
+
extension/popup.html CHANGED
@@ -13,12 +13,20 @@
13
  <header class="bayan-header">
14
  <div class="bayan-header-brand">
15
  <img src="assets/icons/icon48.png" alt="بيان" width="28" height="28" style="border-radius:6px;">
 
16
  <span class="bayan-header-title">بيان</span>
17
  </div>
18
- <div class="bayan-header-status" id="status-indicator">
 
 
 
 
 
 
19
  <span class="bayan-status-dot"></span>
20
  <span class="bayan-status-text" id="status-text">جاهز</span>
21
  </div>
 
22
  </header>
23
 
24
  <!-- ══════════════════════════════════════════════ -->
 
13
  <header class="bayan-header">
14
  <div class="bayan-header-brand">
15
  <img src="assets/icons/icon48.png" alt="بيان" width="28" height="28" style="border-radius:6px;">
16
+ <div style="height:24px; width:2px; background-color:#d1d5db; border-radius:9999px; flex-shrink:0;"></div>
17
  <span class="bayan-header-title">بيان</span>
18
  </div>
19
+
20
+ <div style="display: flex; align-items: center; gap: 12px;">
21
+ <button id="ext-theme-toggle" class="theme-toggle-animated" aria-label="تبديل السمة" type="button">
22
+ <svg class="theme-icon-sun" width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
23
+ <svg class="theme-icon-moon" width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
24
+ </button>
25
+ <div class="bayan-header-status" id="status-indicator">
26
  <span class="bayan-status-dot"></span>
27
  <span class="bayan-status-text" id="status-text">جاهز</span>
28
  </div>
29
+ </div>
30
  </header>
31
 
32
  <!-- ══════════════════════════════════════════════ -->
extension/popup.js CHANGED
@@ -680,3 +680,32 @@ document.addEventListener('DOMContentLoaded', () => {
680
  })();
681
  });
682
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  })();
681
  });
682
 
683
+
684
+
685
+ // ── Theme Toggle Logic ──
686
+ (function initBayanThemeToggle() {
687
+ const toggleBtn = document.getElementById('ext-theme-toggle');
688
+
689
+ // Load theme from storage
690
+ chrome.storage.local.get(['theme'], (result) => {
691
+ const currentTheme = result.theme || 'dark'; // default to dark
692
+ document.documentElement.setAttribute('data-theme', currentTheme);
693
+ });
694
+
695
+ // Sync theme changes instantly across all views
696
+ chrome.storage.onChanged.addListener((changes, namespace) => {
697
+ if (namespace === 'local' && changes.theme) {
698
+ document.documentElement.setAttribute('data-theme', changes.theme.newValue);
699
+ }
700
+ });
701
+
702
+
703
+ if (toggleBtn) {
704
+ toggleBtn.addEventListener('click', () => {
705
+ let theme = document.documentElement.getAttribute('data-theme') || 'dark';
706
+ let targetTheme = theme === 'dark' ? 'light' : 'dark';
707
+ document.documentElement.setAttribute('data-theme', targetTheme);
708
+ chrome.storage.local.set({ theme: targetTheme });
709
+ });
710
+ }
711
+ })();
extension/sidepanel/sidepanel.css CHANGED
@@ -678,3 +678,86 @@ body {
678
  .is-hidden {
679
  display: none !important;
680
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  .is-hidden {
679
  display: none !important;
680
  }
681
+
682
+
683
+ /* Light Theme Variables */
684
+ [data-theme="light"] {
685
+ --bayan-bg: #f9fafb;
686
+ --bayan-surface: #ffffff;
687
+ --bayan-surface-hover: #f3f4f6;
688
+ --bayan-surface-active: #e5e7eb;
689
+ --bayan-border: #e5e7eb;
690
+ --bayan-border-light: #d1d5db;
691
+ --bayan-text: #111827;
692
+ --bayan-text-secondary: #4b5563;
693
+ --bayan-text-muted: #9ca3af;
694
+ --bayan-success: #16a34a;
695
+ --bayan-warning: #d97706;
696
+ }
697
+
698
+ /* Theme Toggle Button Styles */
699
+ .theme-toggle-animated {
700
+ display: flex;
701
+ align-items: center;
702
+ justify-content: center;
703
+ width: 32px;
704
+ height: 32px;
705
+ border: none;
706
+ border-radius: 50%;
707
+ background: var(--sp-surface-hover);
708
+ color: var(--sp-text-secondary);
709
+ cursor: pointer;
710
+ transition: background 0.3s ease, transform 0.3s ease, color 0.3s ease;
711
+ position: relative;
712
+ overflow: hidden;
713
+ margin-right: 8px;
714
+ }
715
+
716
+ .theme-toggle-animated:hover {
717
+ background: var(--sp-primary);
718
+ color: #fff;
719
+ transform: rotate(15deg);
720
+ }
721
+
722
+ .theme-toggle-animated svg {
723
+ transition: transform 0.4s ease, opacity 0.3s ease;
724
+ position: absolute;
725
+ }
726
+
727
+ [data-theme="dark"] .theme-icon-sun {
728
+ transform: rotate(90deg) scale(0);
729
+ opacity: 0;
730
+ }
731
+
732
+ [data-theme="dark"] .theme-icon-moon {
733
+ transform: rotate(0) scale(1);
734
+ opacity: 1;
735
+ }
736
+
737
+ [data-theme="light"] .theme-icon-moon {
738
+ transform: rotate(-90deg) scale(0);
739
+ opacity: 0;
740
+ }
741
+
742
+ [data-theme="light"] .theme-icon-sun {
743
+ transform: rotate(0) scale(1);
744
+ opacity: 1;
745
+ }
746
+
747
+
748
+
749
+ /* Light Theme Variables for Side Panel */
750
+ [data-theme="light"] {
751
+ --sp-bg: #f9fafb;
752
+ --sp-surface: #ffffff;
753
+ --sp-surface-hover: #f3f4f6;
754
+ --sp-surface-active: #e5e7eb;
755
+ --sp-border: #e5e7eb;
756
+ --sp-border-light: #d1d5db;
757
+ --sp-text: #111827;
758
+ --sp-text-secondary: #4b5563;
759
+ --sp-text-muted: #9ca3af;
760
+ --sp-success: #16a34a;
761
+ --sp-warning: #d97706;
762
+ }
763
+
extension/sidepanel/sidepanel.html CHANGED
@@ -13,12 +13,20 @@
13
  <header class="sp-header">
14
  <div class="sp-header-brand">
15
  <img src="../assets/icons/icon48.png" alt="بيان" width="24" height="24" style="border-radius:5px;">
 
16
  <span class="sp-header-title">بيان</span>
17
  </div>
18
- <div class="sp-header-status" id="status-indicator">
 
 
 
 
 
 
19
  <span class="sp-status-dot"></span>
20
  <span class="sp-status-text" id="status-text">جاري الاتصال...</span>
21
  </div>
 
22
  </header>
23
 
24
  <!-- ══════════════════════════════════════════════ -->
 
13
  <header class="sp-header">
14
  <div class="sp-header-brand">
15
  <img src="../assets/icons/icon48.png" alt="بيان" width="24" height="24" style="border-radius:5px;">
16
+ <div style="height:24px; width:2px; background-color:#d1d5db; border-radius:9999px; flex-shrink:0;"></div>
17
  <span class="sp-header-title">بيان</span>
18
  </div>
19
+
20
+ <div style="display: flex; align-items: center; gap: 12px;">
21
+ <button id="ext-theme-toggle" class="theme-toggle-animated" aria-label="تبديل السمة" type="button">
22
+ <svg class="theme-icon-sun" width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
23
+ <svg class="theme-icon-moon" width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
24
+ </button>
25
+ <div class="sp-header-status" id="status-indicator">
26
  <span class="sp-status-dot"></span>
27
  <span class="sp-status-text" id="status-text">جاري الاتصال...</span>
28
  </div>
29
+ </div>
30
  </header>
31
 
32
  <!-- ══════════════════════════════════════════════ -->
extension/sidepanel/sidepanel.js CHANGED
@@ -846,3 +846,32 @@ document.addEventListener('DOMContentLoaded', () => {
846
  });
847
  }
848
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
  });
847
  }
848
  });
849
+
850
+
851
+ // ── Theme Toggle Logic ──
852
+ (function initBayanThemeToggle() {
853
+ const toggleBtn = document.getElementById('ext-theme-toggle');
854
+
855
+ // Load theme from storage
856
+ chrome.storage.local.get(['theme'], (result) => {
857
+ const currentTheme = result.theme || 'dark'; // default to dark
858
+ document.documentElement.setAttribute('data-theme', currentTheme);
859
+ });
860
+
861
+ // Sync theme changes instantly across all views
862
+ chrome.storage.onChanged.addListener((changes, namespace) => {
863
+ if (namespace === 'local' && changes.theme) {
864
+ document.documentElement.setAttribute('data-theme', changes.theme.newValue);
865
+ }
866
+ });
867
+
868
+
869
+ if (toggleBtn) {
870
+ toggleBtn.addEventListener('click', () => {
871
+ let theme = document.documentElement.getAttribute('data-theme') || 'dark';
872
+ let targetTheme = theme === 'dark' ? 'light' : 'dark';
873
+ document.documentElement.setAttribute('data-theme', targetTheme);
874
+ chrome.storage.local.set({ theme: targetTheme });
875
+ });
876
+ }
877
+ })();
generate_icons.py CHANGED
@@ -1,8 +1,8 @@
1
  """Generate extension icons from LOGOS/1.png (gradient bg for best small-size visibility)."""
2
  from PIL import Image
3
 
4
- img = Image.open("LOGOS/1.png")
5
  for size in [16, 32, 48, 128]:
6
- img.resize((size, size), Image.LANCZOS).save(f"extension/assets/icons/icon{size}.png")
7
- print(f"Generated icon{size}.png ({size}x{size}) from LOGOS/1.png")
8
  print("Done!")
 
1
  """Generate extension icons from LOGOS/1.png (gradient bg for best small-size visibility)."""
2
  from PIL import Image
3
 
4
+ img = Image.open("LOGOS/2.png")
5
  for size in [16, 32, 48, 128]:
6
+ img.resize((size, size), Image.LANCZOS).save(f"LOGOS/icon{size}.png")
7
+ print(f"Generated icon{size}.png ({size}x{size}) from LOGOS/2.png")
8
  print("Done!")
src/nlp/punctuation/punctuation_rules.py CHANGED
@@ -56,23 +56,42 @@ def arabic_postprocessing(text: str) -> str:
56
  # Remove colons/semicolons before relative pronouns
57
  text = re.sub(r'[؛:]\s*(التي|الذي|الذين|اللتان|اللذان|اللاتي|اللواتي)', r' \1', text)
58
 
59
- # NEW: Strict Colon Guard
60
- _ALLOWED_COLON_CUES = r'(قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت|وضح|وضحت|أوضح|أوضحت|رد|ردت|التالي|الآتي|مثال|ملاحظة|تنبيه|تحذير|قائلا|قائلة|اسم|العمر|تاريخ|رقم|عاجل|الآتية|التالية)'
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  def _colon_guard(match):
63
- prev_word = match.group(1)
64
- if re.fullmatch(_ALLOWED_COLON_CUES, prev_word):
 
 
 
65
  return match.group(0)
66
- # Strip colons from definite nouns (ال-prefix) and preposition+definite combos
67
- # (لل, بال, فال, وال, كال). These are never valid colon targets.
 
 
 
 
 
68
  if prev_word.startswith(('ال', 'لل', 'بال', 'فال', 'وال', 'كال')):
69
- return f'{prev_word}'
 
70
  return match.group(0)
71
 
72
- text = re.sub(r'([\u0600-\u06FF]+)(\s*:)', _colon_guard, text)
73
-
74
- # Fix misplaced colons for saying verbs (e.g. قال: المعلم -> قال المعلم:)
75
- text = re.sub(r'\b(قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت):?\s+(ال[أ-ي]+|أحمد|محمد|محمود|علي|عمر|خالد|فاطمة|مريم|عائشة|خديجة)\b:?', r'\1 \2:', text)
76
 
77
  # Remove colons after specific non-speech verbs (fallback for verbs without ال)
78
  text = re.sub(r'\b(يقدر|يستطيع|يمكن|يجب|ينبغي|يعتبر|يعد|يرى|يعتقد)\s*:', r'\1 ', text)
 
56
  # Remove colons/semicolons before relative pronouns
57
  text = re.sub(r'[؛:]\s*(التي|الذي|الذين|اللتان|اللذان|اللاتي|اللواتي)', r' \1', text)
58
 
59
+ # 1. Fix misplaced colons (e.g. قال: المعلم -> قال المعلم:)
60
+ # Only applies if a colon is actually present on the verb or the name
61
+ def _fix_misplaced(m):
62
+ verb, col1, name, col2 = m.groups()
63
+ if col1 == ':' or col2 == ':':
64
+ return f"{verb} {name}:"
65
+ return m.group(0)
66
+
67
+ text = re.sub(
68
+ r'\b([وفلس]?(?:قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت))(:?)\s+(ال[أ-ي]+|أحمد|محمد|محمود|علي|عمر|خالد|فاطمة|مريم|عائشة|خديجة)\b(:?)',
69
+ _fix_misplaced, text
70
+ )
71
+
72
+ # 2. Smart Colon Guard (looks up to 6 words back)
73
+ _ALLOWED_COLON_CUES = r'^[وفلس]?(قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت|وضح|وضحت|أوضح|أوضحت|رد|ردت|التالي|الآتي|مثال|ملاحظة|تنبيه|تحذير|قائلا|قائلة|اسم|العمر|تاريخ|رقم|عاجل|الآتية|التالية)$'
74
 
75
  def _colon_guard(match):
76
+ context = match.group(1)
77
+ colon = match.group(2)
78
+
79
+ words = re.findall(r'[\u0600-\u06FFa-zA-Z]+', context)
80
+ if not words:
81
  return match.group(0)
82
+
83
+ prev_word = words[-1]
84
+ last_6_words = words[-6:]
85
+
86
+ if any(re.match(_ALLOWED_COLON_CUES, w) for w in last_6_words):
87
+ return match.group(0)
88
+
89
  if prev_word.startswith(('ال', 'لل', 'بال', 'فال', 'وال', 'كال')):
90
+ return context + " "
91
+
92
  return match.group(0)
93
 
94
+ text = re.sub(r'([^:]+)(:)', _colon_guard, text)
 
 
 
95
 
96
  # Remove colons after specific non-speech verbs (fallback for verbs without ال)
97
  text = re.sub(r'\b(يقدر|يستطيع|يمكن|يجب|ينبغي|يعتبر|يعد|يرى|يعتقد)\s*:', r'\1 ', text)
tests/phase10/reports/phase10_results.json CHANGED
The diff for this file is too large to render. See raw diff