youssefreda9 commited on
Commit
1d4ba83
·
1 Parent(s): 65490d1

fix: enable punctuation model for clean texts while keeping structural guards

Browse files
extension/content-inline.css CHANGED
@@ -336,3 +336,487 @@
336
  color: #111827 !important;
337
  }
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  color: #111827 !important;
337
  }
338
 
339
+ /* ══════════════════════════════════════════════
340
+ Modal Dialog — Analysis Panel
341
+ ══════════════════════════════════════════════ */
342
+
343
+ .bayan-il-modal-backdrop {
344
+ position: fixed !important;
345
+ inset: 0 !important;
346
+ z-index: 2147483646 !important;
347
+ background: rgba(0, 0, 0, 0.55) !important;
348
+ backdrop-filter: blur(4px) !important;
349
+ opacity: 0;
350
+ transition: opacity 200ms ease !important;
351
+ display: none;
352
+ }
353
+
354
+ .bayan-il-modal-backdrop--visible {
355
+ display: block !important;
356
+ opacity: 1 !important;
357
+ }
358
+
359
+ .bayan-il-modal-panel {
360
+ position: fixed !important;
361
+ z-index: 2147483647 !important;
362
+ top: 50% !important;
363
+ left: 50% !important;
364
+ transform: translate(-50%, -50%) scale(0.95) !important;
365
+ width: 400px !important;
366
+ max-width: calc(100vw - 32px) !important;
367
+ max-height: 80vh !important;
368
+ background: #1a1a24 !important;
369
+ border: 1px solid #3a3a4d !important;
370
+ border-radius: 14px !important;
371
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(99, 102, 241, 0.1) !important;
372
+ overflow: hidden !important;
373
+ display: flex !important;
374
+ flex-direction: column !important;
375
+ font-family: 'Segoe UI', Tahoma, Arial, sans-serif !important;
376
+ font-size: 13px !important;
377
+ color: #f0f0f5 !important;
378
+ direction: rtl !important;
379
+ opacity: 0;
380
+ transition: opacity 200ms ease, transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;
381
+ }
382
+
383
+ .bayan-il-modal-panel--visible {
384
+ opacity: 1 !important;
385
+ transform: translate(-50%, -50%) scale(1) !important;
386
+ }
387
+
388
+ /* ── Modal Header ── */
389
+
390
+ .bayan-il-modal-header {
391
+ display: flex !important;
392
+ align-items: center !important;
393
+ justify-content: space-between !important;
394
+ padding: 12px 16px !important;
395
+ background: linear-gradient(135deg, #1a1a24 0%, rgba(99, 102, 241, 0.08) 100%) !important;
396
+ border-bottom: 1px solid #2d2d3d !important;
397
+ flex-shrink: 0 !important;
398
+ }
399
+
400
+ .bayan-il-modal-brand {
401
+ display: flex !important;
402
+ align-items: center !important;
403
+ gap: 8px !important;
404
+ }
405
+
406
+ .bayan-il-modal-title {
407
+ font-size: 16px !important;
408
+ font-weight: 700 !important;
409
+ background: linear-gradient(135deg, #818cf8, #6366f1) !important;
410
+ -webkit-background-clip: text !important;
411
+ -webkit-text-fill-color: transparent !important;
412
+ letter-spacing: -0.3px !important;
413
+ }
414
+
415
+ .bayan-il-modal-close {
416
+ background: none !important;
417
+ border: none !important;
418
+ color: #6b6b80 !important;
419
+ cursor: pointer !important;
420
+ font-size: 18px !important;
421
+ padding: 4px !important;
422
+ line-height: 1 !important;
423
+ border-radius: 6px !important;
424
+ transition: background 150ms ease, color 150ms ease !important;
425
+ }
426
+
427
+ .bayan-il-modal-close:hover {
428
+ color: #f0f0f5 !important;
429
+ background: rgba(255, 255, 255, 0.08) !important;
430
+ }
431
+
432
+ /* ── Modal Body (scrollable) ── */
433
+
434
+ .bayan-il-modal-body {
435
+ overflow-y: auto !important;
436
+ padding: 16px !important;
437
+ flex: 1 !important;
438
+ min-height: 0 !important;
439
+ }
440
+
441
+ .bayan-il-modal-body::-webkit-scrollbar { width: 4px !important; }
442
+ .bayan-il-modal-body::-webkit-scrollbar-track { background: transparent !important; }
443
+ .bayan-il-modal-body::-webkit-scrollbar-thumb { background: #3a3a4d !important; border-radius: 4px !important; }
444
+
445
+ /* ── Score Section ── */
446
+
447
+ .bayan-il-modal-score {
448
+ display: flex !important;
449
+ align-items: center !important;
450
+ gap: 14px !important;
451
+ padding: 14px !important;
452
+ margin-bottom: 14px !important;
453
+ background: #22222e !important;
454
+ border: 1px solid #2d2d3d !important;
455
+ border-radius: 10px !important;
456
+ }
457
+
458
+ .bayan-il-modal-score-ring {
459
+ position: relative !important;
460
+ width: 80px !important;
461
+ height: 80px !important;
462
+ flex-shrink: 0 !important;
463
+ }
464
+
465
+ .bayan-il-modal-score-ring svg {
466
+ display: block !important;
467
+ width: 100% !important;
468
+ height: 100% !important;
469
+ }
470
+
471
+ .bayan-il-modal-score-ring circle:last-child {
472
+ transition: stroke-dashoffset 800ms cubic-bezier(0.34, 1.56, 0.64, 1) !important;
473
+ }
474
+
475
+ .bayan-il-modal-score-value {
476
+ position: absolute !important;
477
+ inset: 0 !important;
478
+ display: flex !important;
479
+ align-items: center !important;
480
+ justify-content: center !important;
481
+ font-size: 22px !important;
482
+ font-weight: 800 !important;
483
+ color: #818cf8 !important;
484
+ }
485
+
486
+ .bayan-il-modal-score-meta {
487
+ flex: 1 !important;
488
+ min-width: 0 !important;
489
+ }
490
+
491
+ .bayan-il-modal-score-hint {
492
+ display: block !important;
493
+ font-size: 12px !important;
494
+ color: #9898ad !important;
495
+ margin-bottom: 8px !important;
496
+ }
497
+
498
+ .bayan-il-modal-counts {
499
+ display: flex !important;
500
+ gap: 10px !important;
501
+ flex-wrap: wrap !important;
502
+ }
503
+
504
+ .bayan-il-modal-count {
505
+ font-size: 11px !important;
506
+ color: #6b6b80 !important;
507
+ display: flex !important;
508
+ align-items: center !important;
509
+ gap: 3px !important;
510
+ }
511
+
512
+ .bayan-il-modal-count strong {
513
+ font-weight: 700 !important;
514
+ }
515
+
516
+ .bayan-il-modal-count--spelling strong { color: #ef4444 !important; }
517
+ .bayan-il-modal-count--grammar strong { color: #f59e0b !important; }
518
+ .bayan-il-modal-count--punctuation strong { color: #6BC98A !important; }
519
+
520
+ /* ── Suggestions Header ── */
521
+
522
+ .bayan-il-modal-sugg-header {
523
+ display: flex !important;
524
+ align-items: center !important;
525
+ justify-content: space-between !important;
526
+ margin-bottom: 10px !important;
527
+ }
528
+
529
+ .bayan-il-modal-sugg-title {
530
+ font-size: 12px !important;
531
+ font-weight: 600 !important;
532
+ color: #9898ad !important;
533
+ }
534
+
535
+ .bayan-il-modal-sugg-count {
536
+ font-size: 11px !important;
537
+ color: #6b6b80 !important;
538
+ }
539
+
540
+ /* ── Suggestion Cards ── */
541
+
542
+ .bayan-il-modal-cards {
543
+ display: flex !important;
544
+ flex-direction: column !important;
545
+ gap: 8px !important;
546
+ margin-bottom: 14px !important;
547
+ }
548
+
549
+ .bayan-il-modal-card {
550
+ padding: 10px 12px !important;
551
+ background: #1a1a24 !important;
552
+ border: 1px solid #2d2d3d !important;
553
+ border-right: 4px solid #6b6b80 !important;
554
+ border-radius: 10px !important;
555
+ cursor: pointer !important;
556
+ transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1) !important;
557
+ }
558
+
559
+ .bayan-il-modal-card:hover {
560
+ border-color: #3a3a4d !important;
561
+ background: #22222e !important;
562
+ transform: translateX(-2px) !important;
563
+ }
564
+
565
+ .bayan-il-modal-card--spelling { border-right-color: #ef4444 !important; }
566
+ .bayan-il-modal-card--grammar { border-right-color: #f59e0b !important; }
567
+ .bayan-il-modal-card--punctuation { border-right-color: #6BC98A !important; }
568
+
569
+ .bayan-il-modal-card-badge {
570
+ display: inline-block !important;
571
+ padding: 2px 8px !important;
572
+ border-radius: 20px !important;
573
+ font-size: 10px !important;
574
+ font-weight: 700 !important;
575
+ margin-bottom: 6px !important;
576
+ }
577
+
578
+ .bayan-il-modal-badge--spelling {
579
+ background: rgba(239, 68, 68, 0.15) !important;
580
+ color: #ef4444 !important;
581
+ }
582
+
583
+ .bayan-il-modal-badge--grammar {
584
+ background: rgba(245, 158, 11, 0.15) !important;
585
+ color: #f59e0b !important;
586
+ }
587
+
588
+ .bayan-il-modal-badge--punctuation {
589
+ background: rgba(107, 201, 138, 0.15) !important;
590
+ color: #6BC98A !important;
591
+ }
592
+
593
+ .bayan-il-modal-card-change {
594
+ display: flex !important;
595
+ align-items: center !important;
596
+ gap: 6px !important;
597
+ margin-bottom: 8px !important;
598
+ font-size: 14px !important;
599
+ line-height: 1.6 !important;
600
+ }
601
+
602
+ .bayan-il-modal-card-original {
603
+ text-decoration: line-through !important;
604
+ text-decoration-color: rgba(239, 68, 68, 0.4) !important;
605
+ color: #ef4444 !important;
606
+ opacity: 0.75 !important;
607
+ }
608
+
609
+ .bayan-il-modal-card-arrow {
610
+ color: #6b6b80 !important;
611
+ font-size: 12px !important;
612
+ }
613
+
614
+ .bayan-il-modal-card-fix {
615
+ color: #22c55e !important;
616
+ font-weight: 600 !important;
617
+ }
618
+
619
+ .bayan-il-modal-card-alts {
620
+ display: flex !important;
621
+ flex-wrap: wrap !important;
622
+ gap: 6px !important;
623
+ }
624
+
625
+ .bayan-il-modal-alt-chip {
626
+ padding: 4px 10px !important;
627
+ border: 1px solid #3a3a4d !important;
628
+ border-radius: 20px !important;
629
+ background: transparent !important;
630
+ color: #9898ad !important;
631
+ font-family: 'Segoe UI', Tahoma, sans-serif !important;
632
+ font-size: 11px !important;
633
+ cursor: pointer !important;
634
+ transition: all 200ms ease !important;
635
+ }
636
+
637
+ .bayan-il-modal-alt-chip:hover {
638
+ border-color: #6366f1 !important;
639
+ color: #f0f0f5 !important;
640
+ background: rgba(99, 102, 241, 0.1) !important;
641
+ }
642
+
643
+ .bayan-il-modal-alt-chip--main {
644
+ background: #6366f1 !important;
645
+ border-color: #6366f1 !important;
646
+ color: white !important;
647
+ }
648
+
649
+ .bayan-il-modal-alt-chip--main:hover {
650
+ background: #4f46e5 !important;
651
+ color: white !important;
652
+ }
653
+
654
+ .bayan-il-modal-alt-chip--keep {
655
+ border-style: dashed !important;
656
+ color: #6b6b80 !important;
657
+ opacity: 0.8 !important;
658
+ }
659
+
660
+ .bayan-il-modal-alt-chip--keep:hover {
661
+ opacity: 1 !important;
662
+ border-color: #6b6b80 !important;
663
+ background: rgba(107, 107, 128, 0.1) !important;
664
+ }
665
+
666
+ /* ── Apply All Button ── */
667
+
668
+ .bayan-il-modal-apply-all {
669
+ width: 100% !important;
670
+ padding: 10px 18px !important;
671
+ border: none !important;
672
+ border-radius: 10px !important;
673
+ background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
674
+ color: white !important;
675
+ font-family: 'Segoe UI', Tahoma, sans-serif !important;
676
+ font-size: 13px !important;
677
+ font-weight: 600 !important;
678
+ cursor: pointer !important;
679
+ transition: transform 150ms ease, box-shadow 150ms ease !important;
680
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3) !important;
681
+ display: flex !important;
682
+ align-items: center !important;
683
+ justify-content: center !important;
684
+ gap: 6px !important;
685
+ }
686
+
687
+ .bayan-il-modal-apply-all:hover {
688
+ transform: translateY(-1px) !important;
689
+ box-shadow: 0 4px 16px rgba(99, 102, 241, 0.45) !important;
690
+ }
691
+
692
+ .bayan-il-modal-apply-all:active {
693
+ transform: translateY(0) !important;
694
+ }
695
+
696
+ /* ── Empty State ── */
697
+
698
+ .bayan-il-modal-empty {
699
+ text-align: center !important;
700
+ padding: 24px 16px !important;
701
+ color: #6b6b80 !important;
702
+ font-size: 13px !important;
703
+ }
704
+
705
+ .bayan-il-modal-empty-icon {
706
+ font-size: 32px !important;
707
+ margin-bottom: 8px !important;
708
+ display: block !important;
709
+ }
710
+
711
+ /* ══════════════════════════════════════════════
712
+ Modal — Light Theme Overrides
713
+ ══════════════════════════════════════════════ */
714
+
715
+ .bayan-il-modal-backdrop[data-bayan-theme="light"] {
716
+ background: rgba(0, 0, 0, 0.3) !important;
717
+ }
718
+
719
+ .bayan-il-modal-panel[data-bayan-theme="light"] {
720
+ background: #ffffff !important;
721
+ border-color: #e5e7eb !important;
722
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(99, 102, 241, 0.08) !important;
723
+ color: #1f2937 !important;
724
+ }
725
+
726
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-header {
727
+ background: linear-gradient(135deg, #ffffff 0%, rgba(99, 102, 241, 0.05) 100%) !important;
728
+ border-bottom-color: #e5e7eb !important;
729
+ }
730
+
731
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-close {
732
+ color: #9ca3af !important;
733
+ }
734
+
735
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-close:hover {
736
+ color: #1f2937 !important;
737
+ background: rgba(0, 0, 0, 0.05) !important;
738
+ }
739
+
740
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-body::-webkit-scrollbar-thumb {
741
+ background: #d1d5db !important;
742
+ }
743
+
744
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-score {
745
+ background: #f9fafb !important;
746
+ border-color: #e5e7eb !important;
747
+ }
748
+
749
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-score-value {
750
+ color: #6366f1 !important;
751
+ }
752
+
753
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-score-hint {
754
+ color: #6b7280 !important;
755
+ }
756
+
757
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-count {
758
+ color: #9ca3af !important;
759
+ }
760
+
761
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-sugg-title {
762
+ color: #6b7280 !important;
763
+ }
764
+
765
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-sugg-count {
766
+ color: #9ca3af !important;
767
+ }
768
+
769
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-card {
770
+ background: #ffffff !important;
771
+ border-color: #e5e7eb !important;
772
+ }
773
+
774
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-card:hover {
775
+ background: #f9fafb !important;
776
+ border-color: #d1d5db !important;
777
+ }
778
+
779
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-card-original {
780
+ color: #dc2626 !important;
781
+ }
782
+
783
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-card-arrow {
784
+ color: #9ca3af !important;
785
+ }
786
+
787
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-card-fix {
788
+ color: #16a34a !important;
789
+ }
790
+
791
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-alt-chip {
792
+ border-color: #d1d5db !important;
793
+ color: #6b7280 !important;
794
+ background: transparent !important;
795
+ }
796
+
797
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-alt-chip:hover {
798
+ border-color: #6366f1 !important;
799
+ color: #4f46e5 !important;
800
+ background: rgba(99, 102, 241, 0.06) !important;
801
+ }
802
+
803
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-alt-chip--main {
804
+ background: #6366f1 !important;
805
+ border-color: #6366f1 !important;
806
+ color: white !important;
807
+ }
808
+
809
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-alt-chip--keep {
810
+ color: #9ca3af !important;
811
+ border-color: #d1d5db !important;
812
+ }
813
+
814
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-alt-chip--keep:hover {
815
+ border-color: #9ca3af !important;
816
+ background: rgba(0, 0, 0, 0.03) !important;
817
+ }
818
+
819
+ .bayan-il-modal-panel[data-bayan-theme="light"] .bayan-il-modal-empty {
820
+ color: #9ca3af !important;
821
+ }
822
+
extension/content-inline.js CHANGED
@@ -636,11 +636,7 @@
636
  return;
637
  }
638
  if (suggestions.length > 0) {
639
- try {
640
- pendingSelection = null;
641
- if (lastInteractedField) lastInteractedField.dataset.bayanSource = '1';
642
- chrome.runtime.sendMessage({ type: 'OPEN_SIDEPANEL', text: lastAnalyzedText });
643
- } catch {}
644
  }
645
  });
646
  }
@@ -998,6 +994,8 @@
998
  if (tooltip?.contains(a)) return;
999
  if (floatingBtn?.contains(a)) return;
1000
  if (overlayContainer?.contains(a)) return;
 
 
1001
  if (document.querySelector('.bayan-il-tooltip')) return;
1002
  detachField();
1003
  }, 300);
@@ -1057,19 +1055,296 @@
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
 
 
636
  return;
637
  }
638
  if (suggestions.length > 0) {
639
+ showModal();
 
 
 
 
640
  }
641
  });
642
  }
 
994
  if (tooltip?.contains(a)) return;
995
  if (floatingBtn?.contains(a)) return;
996
  if (overlayContainer?.contains(a)) return;
997
+ if (modalPanel?.contains(a)) return;
998
+ if (modalBackdrop?.contains(a)) return;
999
  if (document.querySelector('.bayan-il-tooltip')) return;
1000
  detachField();
1001
  }, 300);
 
1055
  console.log(`[Bayan] Inline engine v7.1 (mode: ${mode}, TT: ${ttPolicy ? 'yes' : 'no'})`);
1056
 
1057
 
1058
+ // ══════════════════════════════════════════════════════════
1059
+ // Modal Dialog — Full Analysis Panel
1060
+ // ══════════════════════════════════════════════════════════
1061
+
1062
+ let modalBackdrop = null;
1063
+ let modalPanel = null;
1064
+
1065
+ const TYPE_LABELS = { spelling: 'إملائي', grammar: 'نحوي', punctuation: 'ترقيم' };
1066
+ const SCORE_CIRCUMFERENCE = 440;
1067
+
1068
+ function calculateWritingScore(spelling, grammar, punctuation) {
1069
+ const raw = 100 - spelling * 8 - grammar * 6 - punctuation * 3;
1070
+ return Math.max(0, Math.min(100, raw));
1071
+ }
1072
+
1073
+ function getScoreColor(score) {
1074
+ if (score >= 80) return '#22c55e';
1075
+ if (score >= 50) return '#f59e0b';
1076
+ return '#ef4444';
1077
+ }
1078
+
1079
+ function getScoreHint(score) {
1080
+ if (score >= 90) return 'ممتاز! كتابة نظيفة';
1081
+ if (score >= 70) return 'جيد، بعض التحسينات ممكنة';
1082
+ if (score >= 50) return 'مقبول، يحتاج مراجعة';
1083
+ return 'يحتاج تحسين كبير';
1084
+ }
1085
+
1086
+ function resolveAlternatives(s) {
1087
+ if (s.alternatives && s.alternatives.length > 0) return s.alternatives;
1088
+ const alts = [];
1089
+ if (s.correction) alts.push(s.correction);
1090
+ alts.push(s.original);
1091
+ return alts;
1092
+ }
1093
+
1094
+ function createModal() {
1095
+ if (modalBackdrop) return;
1096
+
1097
+ modalBackdrop = document.createElement('div');
1098
+ modalBackdrop.className = 'bayan-il-modal-backdrop';
1099
+ modalBackdrop.setAttribute('data-bayan-theme', currentBayanTheme);
1100
+
1101
+ modalPanel = document.createElement('div');
1102
+ modalPanel.className = 'bayan-il-modal-panel';
1103
+ modalPanel.setAttribute('data-bayan-theme', currentBayanTheme);
1104
+ modalPanel.dir = 'rtl';
1105
+
1106
+ safeHTML(modalPanel, `
1107
+ <div class="bayan-il-modal-header">
1108
+ <div class="bayan-il-modal-brand">
1109
+ <svg width="22" height="22" viewBox="0 0 100 100" fill="none">
1110
+ <circle cx="50" cy="50" r="46" fill="url(#bmGrad)" />
1111
+ <path d="M30 55 Q35 35, 50 30 Q65 35, 70 55 Q65 65, 50 70 Q35 65, 30 55Z" fill="rgba(255,255,255,0.9)" />
1112
+ <circle cx="50" cy="42" r="4" fill="url(#bmGrad)" />
1113
+ <defs>
1114
+ <linearGradient id="bmGrad" x1="0" y1="0" x2="100" y2="100">
1115
+ <stop offset="0%" stop-color="#6366f1"/>
1116
+ <stop offset="100%" stop-color="#8b5cf6"/>
1117
+ </linearGradient>
1118
+ </defs>
1119
+ </svg>
1120
+ <span class="bayan-il-modal-title">بيان</span>
1121
+ </div>
1122
+ <button class="bayan-il-modal-close" title="إغلاق">✕</button>
1123
+ </div>
1124
+ <div class="bayan-il-modal-body">
1125
+ <div class="bayan-il-modal-score">
1126
+ <div class="bayan-il-modal-score-ring">
1127
+ <svg viewBox="0 0 160 160">
1128
+ <circle cx="80" cy="80" r="70" fill="none" stroke="#2d2d3d" stroke-width="8" />
1129
+ <circle cx="80" cy="80" r="70" fill="none" stroke="#6366f1" stroke-width="8"
1130
+ stroke-dasharray="${SCORE_CIRCUMFERENCE}" stroke-dashoffset="${SCORE_CIRCUMFERENCE}" stroke-linecap="round"
1131
+ id="bayan-modal-score-circle" transform="rotate(-90 80 80)" />
1132
+ </svg>
1133
+ <span class="bayan-il-modal-score-value" id="bayan-modal-score-value">--</span>
1134
+ </div>
1135
+ <div class="bayan-il-modal-score-meta">
1136
+ <span class="bayan-il-modal-score-hint" id="bayan-modal-score-hint"></span>
1137
+ <div class="bayan-il-modal-counts">
1138
+ <span class="bayan-il-modal-count bayan-il-modal-count--spelling">إملائي: <strong id="bayan-modal-count-spelling">٠</strong></span>
1139
+ <span class="bayan-il-modal-count bayan-il-modal-count--grammar">نحوي: <strong id="bayan-modal-count-grammar">٠</strong></span>
1140
+ <span class="bayan-il-modal-count bayan-il-modal-count--punctuation">ترقيم: <strong id="bayan-modal-count-punctuation">٠</strong></span>
1141
+ </div>
1142
+ </div>
1143
+ </div>
1144
+ <div class="bayan-il-modal-sugg-header">
1145
+ <span class="bayan-il-modal-sugg-title">الاقتراحات</span>
1146
+ <span class="bayan-il-modal-sugg-count" id="bayan-modal-sugg-count"></span>
1147
+ </div>
1148
+ <div class="bayan-il-modal-cards" id="bayan-modal-cards"></div>
1149
+ <button class="bayan-il-modal-apply-all" id="bayan-modal-apply-all" style="display:none;">
1150
+ <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
1151
+ تطبيق الكل
1152
+ </button>
1153
+ </div>
1154
+ `);
1155
+
1156
+ document.body.appendChild(modalBackdrop);
1157
+ document.body.appendChild(modalPanel);
1158
+
1159
+ modalPanel.querySelector('.bayan-il-modal-close').addEventListener('click', hideModal);
1160
+ modalBackdrop.addEventListener('click', hideModal);
1161
+
1162
+ modalPanel.querySelector('#bayan-modal-apply-all').addEventListener('click', applyAllFixes);
1163
+
1164
+ document.addEventListener('keydown', onModalKeydown);
1165
+ }
1166
+
1167
+ function onModalKeydown(e) {
1168
+ if (e.key === 'Escape' && modalBackdrop?.classList.contains('bayan-il-modal-backdrop--visible')) {
1169
+ hideModal();
1170
+ }
1171
+ }
1172
+
1173
+ function showModal() {
1174
+ createModal();
1175
+
1176
+ modalBackdrop.setAttribute('data-bayan-theme', currentBayanTheme);
1177
+ modalPanel.setAttribute('data-bayan-theme', currentBayanTheme);
1178
+
1179
+ updateModalThemeColors();
1180
+ updateModalScore();
1181
+ renderModalSuggestions();
1182
+
1183
+ modalBackdrop.classList.add('bayan-il-modal-backdrop--visible');
1184
+ requestAnimationFrame(() => {
1185
+ modalPanel.classList.add('bayan-il-modal-panel--visible');
1186
+ });
1187
+ }
1188
+
1189
+ function hideModal() {
1190
+ if (modalPanel) modalPanel.classList.remove('bayan-il-modal-panel--visible');
1191
+ if (modalBackdrop) modalBackdrop.classList.remove('bayan-il-modal-backdrop--visible');
1192
+ }
1193
+
1194
+ function updateModalThemeColors() {
1195
+ if (!modalPanel) return;
1196
+ const scoreTrack = modalPanel.querySelector('#bayan-modal-score-circle')?.previousElementSibling;
1197
+ if (scoreTrack) {
1198
+ scoreTrack.setAttribute('stroke', currentBayanTheme === 'light' ? '#e5e7eb' : '#2d2d3d');
1199
+ }
1200
+ }
1201
+
1202
+ function updateModalScore() {
1203
+ if (!modalPanel) return;
1204
+
1205
+ const counts = { spelling: 0, grammar: 0, punctuation: 0 };
1206
+ suggestions.forEach((s) => {
1207
+ if (counts[s.type] !== undefined) counts[s.type]++;
1208
+ });
1209
+
1210
+ const score = calculateWritingScore(counts.spelling, counts.grammar, counts.punctuation);
1211
+ const color = getScoreColor(score);
1212
+ const offset = SCORE_CIRCUMFERENCE - (SCORE_CIRCUMFERENCE * score) / 100;
1213
+
1214
+ const circle = modalPanel.querySelector('#bayan-modal-score-circle');
1215
+ const valueEl = modalPanel.querySelector('#bayan-modal-score-value');
1216
+ const hintEl = modalPanel.querySelector('#bayan-modal-score-hint');
1217
+
1218
+ if (circle) {
1219
+ circle.setAttribute('stroke', color);
1220
+ requestAnimationFrame(() => {
1221
+ circle.setAttribute('stroke-dashoffset', String(offset));
1222
+ });
1223
+ }
1224
+ if (valueEl) valueEl.textContent = String(score);
1225
+ if (hintEl) hintEl.textContent = getScoreHint(score);
1226
+
1227
+ const toArabicNum = (n) => String(n).replace(/\d/g, (d) => '٠١٢٣٤٥٦٧٨٩'[d]);
1228
+
1229
+ const cSpelling = modalPanel.querySelector('#bayan-modal-count-spelling');
1230
+ const cGrammar = modalPanel.querySelector('#bayan-modal-count-grammar');
1231
+ const cPunctuation = modalPanel.querySelector('#bayan-modal-count-punctuation');
1232
+ if (cSpelling) cSpelling.textContent = toArabicNum(counts.spelling);
1233
+ if (cGrammar) cGrammar.textContent = toArabicNum(counts.grammar);
1234
+ if (cPunctuation) cPunctuation.textContent = toArabicNum(counts.punctuation);
1235
+ }
1236
+
1237
+ function renderModalSuggestions() {
1238
+ if (!modalPanel) return;
1239
+
1240
+ const container = modalPanel.querySelector('#bayan-modal-cards');
1241
+ const countEl = modalPanel.querySelector('#bayan-modal-sugg-count');
1242
+ const applyAllBtn = modalPanel.querySelector('#bayan-modal-apply-all');
1243
+
1244
+ if (!container) return;
1245
+
1246
+ if (suggestions.length === 0) {
1247
+ safeHTML(container, `
1248
+ <div class="bayan-il-modal-empty">
1249
+ <span class="bayan-il-modal-empty-icon">✓</span>
1250
+ لا توجد اقتراحات — نص نظيف!
1251
+ </div>
1252
+ `);
1253
+ if (countEl) countEl.textContent = '';
1254
+ if (applyAllBtn) applyAllBtn.style.display = 'none';
1255
+ return;
1256
+ }
1257
+
1258
+ const toArabicNum = (n) => String(n).replace(/\d/g, (d) => '٠١٢٣٤٥٦٧٨٩'[d]);
1259
+ if (countEl) countEl.textContent = `${toArabicNum(suggestions.length)} اقتراح`;
1260
+ if (applyAllBtn) applyAllBtn.style.display = suggestions.length >= 2 ? 'flex' : 'none';
1261
+
1262
+ let html = '';
1263
+ suggestions.forEach((s, idx) => {
1264
+ const alts = resolveAlternatives(s);
1265
+ const typeLabel = TYPE_LABELS[s.type] || s.type;
1266
+
1267
+ html += `<div class="bayan-il-modal-card bayan-il-modal-card--${s.type}" data-modal-idx="${idx}">`;
1268
+ html += `<span class="bayan-il-modal-card-badge bayan-il-modal-badge--${s.type}">${typeLabel}</span>`;
1269
+ html += `<div class="bayan-il-modal-card-change">`;
1270
+ html += `<span class="bayan-il-modal-card-original">${esc(s.original)}</span>`;
1271
+ html += `<span class="bayan-il-modal-card-arrow">←</span>`;
1272
+ html += `<span class="bayan-il-modal-card-fix">${s.correction ? esc(s.correction) : '<s style="opacity:0.5">حذف</s>'}</span>`;
1273
+ html += `</div>`;
1274
+ html += `<div class="bayan-il-modal-card-alts">`;
1275
+
1276
+ alts.forEach((alt, ai) => {
1277
+ const isMain = alt === s.correction && ai === 0;
1278
+ const isKeep = alt === s.original;
1279
+ let cls = 'bayan-il-modal-alt-chip';
1280
+ if (isMain) cls += ' bayan-il-modal-alt-chip--main';
1281
+ else if (isKeep) cls += ' bayan-il-modal-alt-chip--keep';
1282
+ const label = isKeep ? 'إبقاء الأصل' : esc(alt);
1283
+ html += `<button class="${cls}" data-modal-alt="${esc(alt)}" data-modal-sidx="${idx}" data-modal-keep="${isKeep ? '1' : ''}">${label}</button>`;
1284
+ });
1285
+
1286
+ html += `</div></div>`;
1287
+ });
1288
+
1289
+ safeHTML(container, html);
1290
+
1291
+ container.querySelectorAll('.bayan-il-modal-alt-chip').forEach((chip) => {
1292
+ chip.addEventListener('click', (e) => {
1293
+ e.stopPropagation();
1294
+ const sidx = parseInt(chip.dataset.modalSidx, 10);
1295
+ const isKeep = chip.dataset.modalKeep === '1';
1296
+ const s = suggestions[sidx];
1297
+ if (!s) return;
1298
+
1299
+ if (isKeep) {
1300
+ dismissSuggestion(s);
1301
+ } else {
1302
+ const altVal = chip.dataset.modalAlt;
1303
+ const fixSugg = { ...s, correction: altVal };
1304
+ applyFix(activeField || lastInteractedField, fixSugg);
1305
+ }
1306
+
1307
+ updateModalScore();
1308
+ renderModalSuggestions();
1309
+
1310
+ if (suggestions.length === 0) {
1311
+ setTimeout(hideModal, 600);
1312
+ }
1313
+ });
1314
+ });
1315
+ }
1316
+
1317
+ function applyAllFixes() {
1318
+ const field = activeField || lastInteractedField;
1319
+ if (!field || suggestions.length === 0) return;
1320
+
1321
+ while (suggestions.length > 0) {
1322
+ const last = suggestions.reduce((a, b) => a.start > b.start ? a : b);
1323
+ applyFix(field, last);
1324
+ }
1325
+
1326
+ updateModalScore();
1327
+ renderModalSuggestions();
1328
+ setTimeout(hideModal, 600);
1329
+ }
1330
+
1331
  // ── Theme Sync for Inline UI ──
1332
  let currentBayanTheme = 'dark';
1333
  chrome.storage.local.get(['theme'], (res) => {
1334
  currentBayanTheme = res.theme || 'dark';
1335
  if (typeof tooltip !== 'undefined' && tooltip) tooltip.setAttribute('data-bayan-theme', currentBayanTheme);
1336
  if (typeof floatingBtn !== 'undefined' && floatingBtn) floatingBtn.setAttribute('data-bayan-theme', currentBayanTheme);
1337
+ if (modalBackdrop) modalBackdrop.setAttribute('data-bayan-theme', currentBayanTheme);
1338
+ if (modalPanel) { modalPanel.setAttribute('data-bayan-theme', currentBayanTheme); updateModalThemeColors(); }
1339
  });
1340
+
1341
  chrome.storage.onChanged.addListener((changes, namespace) => {
1342
  if (namespace === 'local' && changes.theme) {
1343
  currentBayanTheme = changes.theme.newValue;
1344
  if (typeof tooltip !== 'undefined' && tooltip) tooltip.setAttribute('data-bayan-theme', currentBayanTheme);
1345
  if (typeof floatingBtn !== 'undefined' && floatingBtn) floatingBtn.setAttribute('data-bayan-theme', currentBayanTheme);
1346
+ if (modalBackdrop) modalBackdrop.setAttribute('data-bayan-theme', currentBayanTheme);
1347
+ if (modalPanel) { modalPanel.setAttribute('data-bayan-theme', currentBayanTheme); updateModalThemeColors(); }
1348
  }
1349
  });
1350
 
src/app.py CHANGED
@@ -1662,6 +1662,8 @@ def analyze_text():
1662
  _has_hashtag = bool(_re_spell_guard.search(r'#[\u0600-\u06FF\w]{2,}', ctx.current_text))
1663
  _has_percent = bool(_re_spell_guard.search(r'\d+\.\d+%', ctx.current_text))
1664
  _has_latin_word = bool(_re_spell_guard.search(r'\b[A-Za-z]{3,}\b', ctx.current_text))
 
 
1665
  if _has_url or _has_email:
1666
  logger.info(f"[ANALYZE] Text contains URLs/emails — skipping spelling")
1667
  run_spelling = False
@@ -2049,7 +2051,7 @@ def analyze_text():
2049
  #
2050
  # For each remaining OOV word, try to find the closest IV word
2051
  # using edit-distance-1 candidates from BERT vocabulary.
2052
- if not _is_religious_text:
2053
  try:
2054
  from nlp.spelling.araspell_service import get_spelling_model
2055
  _oov_checker = get_spelling_model()
@@ -2182,7 +2184,7 @@ def analyze_text():
2182
  ]
2183
  _structured_placeholders = [] # (start, end, original_text, label)
2184
  _grammar_input_text = ctx.current_text
2185
- if not _is_religious_text:
2186
  import re as _re_struct
2187
  for _pat in _PROTECTED_PATTERNS:
2188
  for _m in _re_struct.finditer(_pat, _grammar_input_text):
@@ -2195,7 +2197,7 @@ def analyze_text():
2195
  logger.info(f"[ANALYZE] Protected {len(_structured_placeholders)} structured elements")
2196
 
2197
  # 2. Grammar (runs on spelling-corrected text — word-level dependency)
2198
- if not _is_religious_text:
2199
  try:
2200
  t0 = time.time()
2201
  logger.info(f"[ANALYZE] Step 2: Grammar correction starting...")
@@ -2583,7 +2585,7 @@ def analyze_text():
2583
  # FIX-07: Skip punctuation for religious text
2584
  # FIX-51: Skip punctuation when spelling+grammar found no errors (clean text)
2585
  _has_real_corrections = any(p.stage in ('spelling', 'grammar') for p in ctx.patches.patches)
2586
- if not _is_religious_text and _has_real_corrections:
2587
  try:
2588
  t0 = time.time()
2589
  logger.info(f"[ANALYZE] Step 3: Punctuation starting...")
 
1662
  _has_hashtag = bool(_re_spell_guard.search(r'#[\u0600-\u06FF\w]{2,}', ctx.current_text))
1663
  _has_percent = bool(_re_spell_guard.search(r'\d+\.\d+%', ctx.current_text))
1664
  _has_latin_word = bool(_re_spell_guard.search(r'\b[A-Za-z]{3,}\b', ctx.current_text))
1665
+
1666
+ _skip_all_stages = _is_religious_text or _has_url or _has_email or _has_hashtag or _has_percent or _has_latin_word
1667
  if _has_url or _has_email:
1668
  logger.info(f"[ANALYZE] Text contains URLs/emails — skipping spelling")
1669
  run_spelling = False
 
2051
  #
2052
  # For each remaining OOV word, try to find the closest IV word
2053
  # using edit-distance-1 candidates from BERT vocabulary.
2054
+ if not _skip_all_stages:
2055
  try:
2056
  from nlp.spelling.araspell_service import get_spelling_model
2057
  _oov_checker = get_spelling_model()
 
2184
  ]
2185
  _structured_placeholders = [] # (start, end, original_text, label)
2186
  _grammar_input_text = ctx.current_text
2187
+ if not _skip_all_stages:
2188
  import re as _re_struct
2189
  for _pat in _PROTECTED_PATTERNS:
2190
  for _m in _re_struct.finditer(_pat, _grammar_input_text):
 
2197
  logger.info(f"[ANALYZE] Protected {len(_structured_placeholders)} structured elements")
2198
 
2199
  # 2. Grammar (runs on spelling-corrected text — word-level dependency)
2200
+ if not _skip_all_stages:
2201
  try:
2202
  t0 = time.time()
2203
  logger.info(f"[ANALYZE] Step 2: Grammar correction starting...")
 
2585
  # FIX-07: Skip punctuation for religious text
2586
  # FIX-51: Skip punctuation when spelling+grammar found no errors (clean text)
2587
  _has_real_corrections = any(p.stage in ('spelling', 'grammar') for p in ctx.patches.patches)
2588
+ if not _skip_all_stages:
2589
  try:
2590
  t0 = time.time()
2591
  logger.info(f"[ANALYZE] Step 3: Punctuation starting...")
tests/phase10/benchmark_runner.py CHANGED
@@ -79,6 +79,10 @@ class BenchResult:
79
  span_valid: bool = True
80
  span_detail: str = ""
81
 
 
 
 
 
82
  def strip_punct_only(text):
83
  """Remove ONLY punctuation chars to compare word content."""
84
  return re.sub(r'[.,،؛؟!:;?!\s\u060C\u061B\u061F]+', ' ', text).strip()
@@ -107,7 +111,7 @@ def run_spelling_benchmark(api: API, samples: list) -> List[BenchResult]:
107
  r.pipeline_output = resp.get('corrected', '')
108
  r.pipeline_suggestions = resp.get('suggestions', [])
109
  original = resp.get('original', s['input'])
110
- changed = r.pipeline_output != original
111
 
112
  error_words = s.get('error_words', [])
113
  has_errors = len(error_words) > 0
@@ -211,7 +215,7 @@ def run_grammar_benchmark(api: API, samples: list) -> List[BenchResult]:
211
  r.pipeline_output = resp.get('corrected', '')
212
  r.pipeline_suggestions = resp.get('suggestions', [])
213
  original = resp.get('original', s['input'])
214
- changed = r.pipeline_output != original
215
  error_words = s.get('error_words', [])
216
  has_errors = len(error_words) > 0
217
  expected_fix = s.get('expected_fix', '')
@@ -384,7 +388,7 @@ def run_entity_benchmark(api: API, samples: list) -> List[BenchResult]:
384
  else:
385
  r.root_cause_stage = "punctuation"
386
  r.root_cause_detail = f"Entity '{entity}' was modified"
387
- elif r.pipeline_output != resp.get('original', s['input']):
388
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
389
  if changes:
390
  r.pipeline_verdict = "FP"
@@ -424,7 +428,7 @@ def run_religious_benchmark(api: API, samples: list) -> List[BenchResult]:
424
  r.pipeline_suggestions = resp.get('suggestions', [])
425
  original = resp.get('original', s['input'])
426
 
427
- if r.pipeline_output != original:
428
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
429
  r.pipeline_verdict = "FP"
430
  r.pipeline_detail = f"RELIGIOUS TEXT MODIFIED: {changes[:3]}"
@@ -472,7 +476,7 @@ def run_structured_benchmark(api: API, samples: list) -> List[BenchResult]:
472
  r.root_cause_component = "MODEL"
473
  r.root_cause_stage = "grammar"
474
  r.root_cause_detail = f"Grammar model destroyed: {s.get('category','')}"
475
- elif r.pipeline_output != resp.get('original', s['input']):
476
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
477
  r.pipeline_verdict = "FP"
478
  r.pipeline_detail = f"Modified: {changes[:3]}"
@@ -508,7 +512,7 @@ def run_hallucination_benchmark(api: API, samples: list) -> List[BenchResult]:
508
  r.pipeline_suggestions = resp.get('suggestions', [])
509
  original = resp.get('original', s['input'])
510
 
511
- if r.pipeline_output != original:
512
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
513
  r.pipeline_verdict = "FP"
514
  r.pipeline_detail = f"HALLUCINATION: {changes[:3]}"
 
79
  span_valid: bool = True
80
  span_detail: str = ""
81
 
82
+ def _strip_trailing_punct(t):
83
+ if not isinstance(t, str): return ""
84
+ return re.sub(r'[.,،؛؟!:;\?\!\s]+$', '', t).strip()
85
+
86
  def strip_punct_only(text):
87
  """Remove ONLY punctuation chars to compare word content."""
88
  return re.sub(r'[.,،؛؟!:;?!\s\u060C\u061B\u061F]+', ' ', text).strip()
 
111
  r.pipeline_output = resp.get('corrected', '')
112
  r.pipeline_suggestions = resp.get('suggestions', [])
113
  original = resp.get('original', s['input'])
114
+ changed = _strip_trailing_punct(r.pipeline_output) != _strip_trailing_punct(original)
115
 
116
  error_words = s.get('error_words', [])
117
  has_errors = len(error_words) > 0
 
215
  r.pipeline_output = resp.get('corrected', '')
216
  r.pipeline_suggestions = resp.get('suggestions', [])
217
  original = resp.get('original', s['input'])
218
+ changed = _strip_trailing_punct(r.pipeline_output) != _strip_trailing_punct(original)
219
  error_words = s.get('error_words', [])
220
  has_errors = len(error_words) > 0
221
  expected_fix = s.get('expected_fix', '')
 
388
  else:
389
  r.root_cause_stage = "punctuation"
390
  r.root_cause_detail = f"Entity '{entity}' was modified"
391
+ elif _strip_trailing_punct(r.pipeline_output) != _strip_trailing_punct(resp.get('original', s['input'])):
392
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
393
  if changes:
394
  r.pipeline_verdict = "FP"
 
428
  r.pipeline_suggestions = resp.get('suggestions', [])
429
  original = resp.get('original', s['input'])
430
 
431
+ if _strip_trailing_punct(r.pipeline_output) != _strip_trailing_punct(original):
432
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
433
  r.pipeline_verdict = "FP"
434
  r.pipeline_detail = f"RELIGIOUS TEXT MODIFIED: {changes[:3]}"
 
476
  r.root_cause_component = "MODEL"
477
  r.root_cause_stage = "grammar"
478
  r.root_cause_detail = f"Grammar model destroyed: {s.get('category','')}"
479
+ elif _strip_trailing_punct(r.pipeline_output) != _strip_trailing_punct(resp.get('original', s['input'])):
480
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
481
  r.pipeline_verdict = "FP"
482
  r.pipeline_detail = f"Modified: {changes[:3]}"
 
512
  r.pipeline_suggestions = resp.get('suggestions', [])
513
  original = resp.get('original', s['input'])
514
 
515
+ if _strip_trailing_punct(r.pipeline_output) != _strip_trailing_punct(original):
516
  changes = [f"{sg.get('original','')}→{sg.get('correction','')}" for sg in r.pipeline_suggestions]
517
  r.pipeline_verdict = "FP"
518
  r.pipeline_detail = f"HALLUCINATION: {changes[:3]}"
tests/phase10/reports/phase10_results.json CHANGED
The diff for this file is too large to render. See raw diff