translate1 / index.html
joermd's picture
Update index.html
7d90293 verified
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>نظام المقارنة المطور - شركة الريحان للترجمة</title>
<!-- استيراد مكتبات Tailwind وFont Awesome وMammoth وPDF.js -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
/* كل أكواد CSS السابقة ظلت كما هي */
/* ================================
تنسيقات الحركات والتأثيرات
================================= */
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.animate-gradient {
background-size: 200% 200%;
animation: gradient 15s ease infinite;
}
.transition-all { transition: all 0.3s ease-in-out; }
.animate-scale { transition: transform 0.2s ease-in-out; }
.animate-scale:hover { transform: scale(1.02); }
.pulse-animation { animation: pulse 2s infinite; }
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(156,39,176,0.4); }
70% { box-shadow: 0 0 0 10px rgba(156,39,176,0); }
100% { box-shadow: 0 0 0 0 rgba(156,39,176,0); }
}
@keyframes highlight-pulse {
0% { opacity: 0.7; }
50% { opacity: 1; }
100% { opacity: 0.7; }
}
/* ================================
تنسيقات النصوص والتحديد المحسنة
================================= */
.text-comparison {
line-height: 2;
white-space: pre-wrap;
padding: 1rem;
border-radius: 8px;
background-color: #fdfdfd;
box-shadow: inset 0 0 3px rgba(0,0,0,0.1);
}
.highlight-number {
background-color: #FDE68A;
padding: 0 4px;
border-radius: 3px;
font-weight: bold;
cursor: pointer;
position: relative;
display: inline-block;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
animation: highlight-pulse 3s ease-in-out infinite;
border-bottom: 2px solid #F59E0B;
}
.highlight-missing {
background-color: #BFDBFE;
padding: 0 4px;
border-radius: 3px;
font-style: italic;
cursor: pointer;
position: relative;
display: inline-block;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
animation: highlight-pulse 3s ease-in-out infinite;
border-bottom: 2px solid #3B82F6;
}
.highlight-meaning {
background-color: #fecaca;
color: #B91C1C;
padding: 0 4px;
border-radius: 3px;
font-weight: bold;
cursor: pointer;
position: relative;
display: inline-block;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
animation: highlight-pulse 3s ease-in-out infinite;
border-bottom: 2px solid #EF4444;
}
/* تحسين علامات وأيقونات التحديد - أكثر وضوحا */
.highlight-number::before,
.highlight-missing::before,
.highlight-meaning::before {
content: "";
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
right: -10px;
top: 50%;
transform: translateY(-50%);
animation: pulse 2s infinite;
}
.highlight-number::before {
background-color: #F59E0B;
}
.highlight-missing::before {
background-color: #3B82F6;
}
.highlight-meaning::before {
background-color: #EF4444;
}
/* تمييز النص المفقود تماما بشكل مختلف وأكثر وضوحا */
.completely-missing {
background-color: #93C5FD;
color: #1E3A8A;
padding: 2px 8px;
border-radius: 4px;
margin: 0 2px;
font-weight: bold;
border-right: 3px solid #2563EB;
border-left: 1px solid #2563EB;
display: inline-block;
position: relative;
animation: highlight-pulse 3s ease-in-out infinite;
}
/* تمييز النص المفقود جزئيا بشكل مختلف وأكثر وضوحا */
.partially-missing {
background-color: #DBEAFE;
color: #1E40AF;
padding: 2px 8px;
border-radius: 4px;
margin: 0 2px;
font-style: italic;
border-bottom: 2px dashed #3B82F6;
display: inline-block;
animation: highlight-pulse 3s ease-in-out infinite;
}
/* إضافة أيقونات داخل التحديد لتوضيح نوع الخطأ */
.highlight-number::after {
content: "١٢٣";
font-size: 8px;
position: absolute;
top: -8px;
left: 0;
background: #F59E0B;
color: white;
padding: 0 3px;
border-radius: 3px;
font-weight: bold;
}
.highlight-missing::after {
content: "...";
font-size: 8px;
position: absolute;
top: -8px;
left: 0;
background: #3B82F6;
color: white;
padding: 0 3px;
border-radius: 3px;
font-weight: bold;
}
.highlight-meaning::after {
content: "!";
font-size: 8px;
position: absolute;
top: -8px;
left: 0;
background: #EF4444;
color: white;
padding: 0 3px;
border-radius: 3px;
font-weight: bold;
}
/* ================================
تنسيق عرض السطور في المعاينة - محسن
================================= */
.split-view {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.line-item {
display: flex;
align-items: flex-start;
margin-bottom: 0.75rem;
padding: 0.5rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.line-item:hover {
background-color: #f1f5f9;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transform: translateY(-1px);
}
.line-number {
width: 40px;
font-weight: bold;
color: #4B5563;
flex-shrink: 0;
background-color: #e5e7eb;
padding: 0 5px;
border-radius: 3px;
text-align: center;
margin-left: 8px;
}
.line-text {
flex: 1;
line-height: 1.8;
}
/* ================================
تحسين تنسيق البطاقات والعناصر
================================= */
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 10px 10px -5px rgba(59, 130, 246, 0.04);
transform: translateY(-2px);
}
/* ================================
تنسيقات المسودة المخفية والتقسيمات
================================= */
.collapsible-section {
border: 1px solid #e5e7eb;
border-radius: 8px;
margin-bottom: 12px;
overflow: hidden;
background-color: #f9fafb;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
transition: all 0.3s ease;
}
.collapsible-section:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.08);
}
.section-header {
background-color: #f3f4f6;
padding: 14px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
transition: background-color 0.2s;
}
.section-header:hover {
background-color: #e5e7eb;
}
.section-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
padding: 0 16px;
}
.section-content.open {
max-height: 1000px;
padding: 16px;
transition: max-height 0.5s ease-in, padding 0.3s ease-in;
}
.draft-marker {
display: inline-block;
background-color: #e5e7eb;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.75rem;
margin-right: 8px;
color: #4b5563;
}
.sync-word {
color: #4f46e5;
font-weight: 500;
background-color: rgba(79, 70, 229, 0.1);
padding: 0 3px;
border-radius: 3px;
}
.paragraph-section {
border-left: 3px solid #d1d5db;
padding-left: 16px;
margin-bottom: 20px;
position: relative;
}
.paragraph-section::before {
content: "";
position: absolute;
width: 10px;
height: 10px;
background-color: #d1d5db;
border-radius: 50%;
left: -6.5px;
top: 0;
}
/* ================================
تنسيقات جديدة للشعار والعرض المحسن
================================= */
.logo-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
}
.logo {
width: 90px;
height: 90px;
border-radius: 50%;
background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
margin-left: 1.5rem;
position: relative;
overflow: hidden;
}
.logo::after {
content: "ر";
font-size: 48px;
font-weight: bold;
color: white;
text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
}
.logo::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(circle at 70% 30%, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 60%);
}
.company-name {
font-size: 2rem;
font-weight: bold;
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
/* تحسين طريقة العرض المقسمة للنصوص */
.segment-comparison {
border: 1px solid #e5e7eb;
border-radius: 12px;
margin-bottom: 1.5rem;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transition: all 0.3s ease;
}
.segment-comparison:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.segment-header {
background-color: #f3f4f6;
padding: 1rem 1.25rem;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
}
.segment-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
background-color: #e5e7eb;
}
.segment-source, .segment-target {
background-color: #fff;
padding: 1.25rem;
transition: background-color 0.2s ease;
position: relative;
}
.segment-source {
background-color: #f0f9ff;
border-left: 3px solid #93c5fd;
}
.segment-target {
background-color: #fdf2f8;
border-right: 3px solid #fbcfe8;
}
.segment-source:hover, .segment-target:hover {
background-color: #f8fafc;
}
.segment-source::after, .segment-target::after {
content: "";
position: absolute;
bottom: 0;
height: 3px;
width: 0;
transition: width 0.3s ease;
}
.segment-source::after {
background-color: #3b82f6;
right: 0;
}
.segment-target::after {
background-color: #ec4899;
left: 0;
}
.segment-source:hover::after, .segment-target:hover::after {
width: 100%;
}
.segment-notes {
grid-column: span 2;
background-color: #fffbeb;
padding: 1rem 1.25rem;
border-top: 1px solid #e5e7eb;
font-size: 0.95rem;
position: relative;
}
.segment-notes::before {
content: "ملاحظات";
position: absolute;
top: -10px;
right: 20px;
background-color: #fbbf24;
color: #fff;
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
.segment-reference {
grid-column: span 2;
background-color: #f0fdf4;
padding: 1rem 1.25rem;
border-top: 1px solid #e5e7eb;
font-size: 0.95rem;
color: #166534;
position: relative;
}
.segment-reference::before {
content: "مرجع";
position: absolute;
top: -10px;
right: 20px;
background-color: #22c55e;
color: #fff;
padding: 2px 10px;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
.segment-tag {
display: inline-block;
padding: 0.35rem 0.75rem;
border-radius: 9999px;
font-size: 0.8rem;
font-weight: 500;
margin-right: 0.5rem;
position: relative;
padding-right: 25px;
}
.segment-tag::before {
content: "";
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
.tag-error {
background-color: #fee2e2;
color: #b91c1c;
}
.tag-error::before {
background-color: #ef4444;
}
.tag-warning {
background-color: #fef3c7;
color: #92400e;
}
.tag-warning::before {
background-color: #f59e0b;
}
.tag-info {
background-color: #dbeafe;
color: #1e40af;
}
.tag-info::before {
background-color: #3b82f6;
}
.tag-success {
background-color: #d1fae5;
color: #065f46;
}
.tag-success::before {
background-color: #10b981;
}
/* ================================
تنسيقات OCR محسنة
================================= */
.pdf-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
margin-top: 20px;
}
.pdf-page {
border: 1px solid #ddd;
border-radius: 6px;
padding: 8px;
position: relative;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.pdf-page:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
border-color: #bfdbfe;
}
.pdf-page.selected {
border: 2px solid #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}
.pdf-page img {
width: 100%;
height: auto;
border-radius: 4px;
object-fit: cover;
}
.page-number {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.7);
color: white;
text-align: center;
font-size: 12px;
padding: 3px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
/* تنسيقات النص المستخرج */
.result-text {
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
direction: rtl;
border: 1px solid #ddd;
padding: 1rem;
border-radius: 8px;
background-color: #f8f9fa;
font-size: 1.05rem;
line-height: 1.8;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}
.result-text:focus {
border-color: #3b82f6;
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3), inset 0 1px 3px rgba(0,0,0,0.1);
}
/* تنسيقات معاينة الصفحات */
.page-preview {
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
transition: all 0.3s;
}
.page-preview:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.1);
}
.page-preview h4 {
background-color: #f0f8ff;
padding: 8px 12px;
border-radius: 6px;
margin-bottom: 12px;
font-weight: 600;
color: #1e40af;
border-right: 3px solid #3b82f6;
}
/* شريط التقدم والمؤشرات */
.progress {
height: 0.5rem;
border-radius: 9999px;
overflow: hidden;
background-color: #e5e7eb;
}
.progress-bar {
height: 100%;
border-radius: 9999px;
background: linear-gradient(90deg, #3b82f6, #2563eb);
transition: width 0.4s ease;
}
.progress-bar.success {
background: linear-gradient(90deg, #10b981, #059669);
}
.progress-bar.warning {
background: linear-gradient(90deg, #f59e0b, #d97706);
}
.progress-bar.error {
background: linear-gradient(90deg, #ef4444, #dc2626);
}
/* تنسيقات الإحصائيات */
.stats-badge {
background: linear-gradient(135deg, #3b82f6, #1e40af);
color: white;
font-size: 14px;
padding: 6px 12px;
border-radius: 20px;
margin-right: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.stats-badge::before {
content: "";
position: absolute;
top: 0;
left: -50%;
width: 150%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transform: skewX(-25deg);
animation: shine 2s infinite;
}
@keyframes shine {
0% { left: -50%; }
100% { left: 150%; }
}
.stats-badge:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.stats-container {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-bottom: 20px;
gap: 10px;
}
/* تنسيقات خاصة بترجمة الريحان */
.bg-white-classic {
background-color: #ffffff;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
border-radius: 8px;
}
/* تنسيقات جديدة لشرح الأخطاء */
.error-popup {
position: fixed;
bottom: 20px;
right: 20px;
max-width: 450px;
width: 90%;
background-color: white;
border-radius: 12px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.25);
z-index: 1000;
overflow: hidden;
display: none; /* Hidden by default */
transform: translateY(10px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.error-popup.show {
transform: translateY(0);
opacity: 1;
}
.error-popup-header {
background: linear-gradient(135deg, #3b82f6, #1e40af);
color: white;
padding: 12px 18px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.error-popup-body {
padding: 18px;
max-height: 300px;
overflow-y: auto;
}
.error-popup-close {
cursor: pointer;
background: none;
border: none;
color: white;
font-size: 20px;
transition: transform 0.2s;
}
.error-popup-close:hover {
transform: scale(1.2);
}
/* تنسيقات جديدة لتمييز الجملة التي بها خطأ */
.sentence-with-error {
border-bottom: 2px dotted #b91c1c;
padding-bottom: 3px;
position: relative;
}
.sentence-with-error:hover::after {
content: "انقر لرؤية التفاصيل";
position: absolute;
bottom: -25px;
right: 0;
background-color: #fef2f2;
color: #b91c1c;
padding: 3px 8px;
border-radius: 4px;
font-size: 12px;
z-index: 10;
box-shadow: 0 2px 5px rgba(0,0,0,0.15);
}
/* تنسيقات قسم توضيح الأخطاء */
.explanation-panel {
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 20px;
margin-top: 24px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
position: relative;
}
.explanation-panel::before {
content: "";
position: absolute;
top: -10px;
right: 30px;
width: 20px;
height: 20px;
background-color: #f8fafc;
border-top: 1px solid #e2e8f0;
border-right: 1px solid #e2e8f0;
transform: rotate(-45deg);
}
.explanation-panel h5 {
font-weight: 600;
margin-bottom: 12px;
color: #1e40af;
font-size: 1.1rem;
border-bottom: 2px solid #dbeafe;
padding-bottom: 8px;
}
/* ================================
تنسيقات طرق العرض المنفصلة
================================= */
.view-container {
margin-top: 1.5rem;
display: none; /* مخفي افتراضيًا */
animation: fadeIn 0.5s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.view-container.active {
display: block;
}
.view-selector {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.view-btn {
padding: 0.75rem 1.5rem;
background-color: #e5e7eb;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
white-space: nowrap;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
position: relative;
overflow: hidden;
}
.view-btn:hover {
background-color: #d1d5db;
transform: translateY(-2px);
box-shadow: 0 3px 6px rgba(0,0,0,0.1);
}
.view-btn.active {
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
box-shadow: 0 3px 8px rgba(59, 130, 246, 0.3);
}
.view-btn.active::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background-color: rgba(255,255,255,0.5);
}
.view-btn i {
margin-left: 10px;
font-size: 1.1rem;
}
/* ================================
تنسيقات فلتر الأخطاء - محسنة
================================= */
.error-filters {
display: flex;
gap: 0.75rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.error-filter {
padding: 0.6rem 1.2rem;
border-radius: 10px;
font-size: 0.95rem;
cursor: pointer;
display: flex;
align-items: center;
transition: all 0.3s;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.error-filter.active {
border-width: 2px;
transform: translateY(-2px);
box-shadow: 0 3px 8px rgba(0,0,0,0.1);
}
.error-filter i {
margin-left: 8px;
font-size: 1.1rem;
}
.filter-all {
background-color: #f3f4f6;
border: 1px solid #d1d5db;
color: #4b5563;
}
.filter-all.active {
background-color: #e5e7eb;
border-color: #9ca3af;
color: #1f2937;
}
.filter-numbers {
background-color: #fef3c7;
border: 1px solid #fcd34d;
color: #92400e;
}
.filter-numbers.active {
background-color: #fde68a;
border-color: #f59e0b;
}
.filter-missing {
background-color: #dbeafe;
border: 1px solid #93c5fd;
color: #1e40af;
}
.filter-missing.active {
background-color: #bfdbfe;
border-color: #3b82f6;
}
.filter-meaning {
background-color: #fee2e2;
border: 1px solid #fca5a5;
color: #b91c1c;
}
.filter-meaning.active {
background-color: #fecaca;
border-color: #ef4444;
}
/* تحسينات على العرض الكلاسيكي */
.classic-view-improved .text-comparison {
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 1.5rem;
line-height: 2.2;
font-size: 1.1rem;
transition: all 0.3s ease;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.classic-view-improved .text-comparison:hover {
box-shadow: 0 5px 10px rgba(0,0,0,0.08);
}
.classic-view-improved h4 {
position: relative;
padding-right: 1.5rem;
margin-bottom: 1.2rem;
display: inline-block;
font-weight: 600;
}
.classic-view-improved h4::before {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 0.85rem;
height: 0.85rem;
border-radius: 50%;
}
.classic-view-improved .source-title::before {
background-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
.classic-view-improved .target-title::before {
background-color: #ec4899;
box-shadow: 0 0 0 3px rgba(236, 72, 153, 0.2);
}
/* تحسينات للتوافق بين الفقرات */
.aligned-paragraphs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2px;
background-color: #e5e7eb;
border-radius: 10px;
overflow: hidden;
margin-bottom: 1.25rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.paragraph-ar, .paragraph-en {
background-color: #fff;
padding: 1.2rem;
white-space: pre-wrap;
transition: background-color 0.2s;
line-height: 1.8;
}
.paragraph-ar:hover, .paragraph-en:hover {
background-color: #f9fafb;
}
.paragraph-ar {
background-color: #f0f9ff;
direction: rtl;
border-left: 3px solid #bfdbfe;
position: relative;
}
.paragraph-ar::after {
content: "المصدر";
position: absolute;
top: 0;
right: 0;
background-color: #3b82f6;
color: white;
padding: 2px 8px;
border-bottom-left-radius: 6px;
font-size: 10px;
font-weight: bold;
}
.paragraph-en {
background-color: #fdf2f8;
direction: ltr;
border-right: 3px solid #fbcfe8;
position: relative;
}
.paragraph-en::after {
content: "الهدف";
position: absolute;
top: 0;
left: 0;
background-color: #ec4899;
color: white;
padding: 2px 8px;
border-bottom-right-radius: 6px;
font-size: 10px;
font-weight: bold;
}
/* ================================
تنسيقات العرض التفاعلي المُحسّن
================================= */
.diff-source-target {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
margin-top: 1.5rem;
}
.diff-panel {
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
transition: all 0.3s;
}
.diff-panel:hover {
box-shadow: 0 5px 10px rgba(0,0,0,0.08);
}
.diff-panel-header {
font-weight: 600;
margin-bottom: 0.75rem;
color: #1e40af;
display: flex;
align-items: center;
border-bottom: 2px solid #dbeafe;
padding-bottom: 8px;
}
.diff-panel-header i {
margin-left: 0.75rem;
font-size: 1.1rem;
}
.diff-source {
background-color: #f0f9ff;
border-color: #bfdbfe;
}
.diff-target {
background-color: #fdf2f8;
border-color: #fbcfe8;
}
.diff-reference {
background-color: #f0fdf4;
border: 1px solid #86efac;
border-radius: 12px;
padding: 1.25rem;
margin-top: 1.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
transition: all 0.3s;
}
.diff-reference:hover {
box-shadow: 0 5px 10px rgba(0,0,0,0.08);
}
.reference-header {
font-weight: 600;
margin-bottom: 0.75rem;
color: #166534;
display: flex;
align-items: center;
border-bottom: 2px solid #bbf7d0;
padding-bottom: 8px;
}
.reference-header i {
margin-left: 0.75rem;
font-size: 1.1rem;
}
/* =============================
تنسيقات تحديد النص
============================= */
/* تحديد مبدئي للأخطاء في المسودة */
.preliminary-highlight-number {
background-color: rgba(253, 224, 71, 0.4);
padding: 0 4px;
border-radius: 3px;
position: relative;
}
.preliminary-highlight-missing {
background-color: rgba(147, 197, 253, 0.4);
padding: 0 4px;
border-radius: 3px;
position: relative;
}
.preliminary-highlight-meaning {
background-color: rgba(252, 165, 165, 0.4);
padding: 0 4px;
border-radius: 3px;
position: relative;
}
/* ملخص التحليل محسن */
.analysis-summary {
background-color: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
margin-bottom: 1.5rem;
}
.analysis-summary-header {
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
padding: 16px 20px;
font-weight: bold;
font-size: 1.1rem;
display: flex;
align-items: center;
}
.analysis-summary-header i {
margin-left: 10px;
font-size: 1.2rem;
}
.analysis-summary-body {
padding: 20px;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.summary-card {
background-color: #f9fafb;
border-radius: 8px;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
transition: all 0.3s;
border: 1px solid #e5e7eb;
}
.summary-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.summary-card-header {
font-size: 3rem;
font-weight: bold;
margin-bottom: 8px;
}
.summary-card-title {
font-size: 0.9rem;
color: #4b5563;
display: flex;
align-items: center;
}
.summary-card-title i {
margin-left: 6px;
}
.error-groups {
border-top: 1px solid #e5e7eb;
padding-top: 16px;
}
.error-group {
margin-bottom: 20px;
}
.error-group-header {
display: flex;
align-items: center;
font-weight: bold;
margin-bottom: 10px;
padding-bottom: 6px;
border-bottom: 2px solid #f3f4f6;
}
.error-group-header i {
margin-left: 8px;
}
.error-item {
background-color: #f9fafb;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
border-right: 3px solid;
transition: all 0.2s;
}
.error-item:hover {
transform: translateX(-5px);
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.error-item-header {
font-weight: bold;
margin-bottom: 6px;
display: flex;
justify-content: space-between;
}
.error-item-body {
background-color: white;
padding: 8px;
border-radius: 4px;
border: 1px solid #e5e7eb;
}
.error-item-footer {
margin-top: 8px;
font-size: 0.85rem;
color: #6b7280;
font-style: italic;
}
.error-number {
border-right-color: #f59e0b;
}
.error-missing {
border-right-color: #3b82f6;
}
.error-meaning {
border-right-color: #ef4444;
}
/* حقوق الملكية - جديد */
.copyright {
text-align: center;
padding: 15px 0;
font-size: 0.9rem;
color: #6b7280;
border-top: 1px solid #e5e7eb;
margin-top: 40px;
}
.copyright a {
color: #3b82f6;
text-decoration: none;
}
.copyright a:hover {
text-decoration: underline;
}
/* ================================
تنسيقات جديدة لاختيار نوع الملف
================================= */
.file-type-selector {
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 1rem;
margin-bottom: 1.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.file-type-options {
display: flex;
gap: 1rem;
margin-top: 0.75rem;
flex-wrap: wrap;
}
.file-type-option {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
background-color: #fff;
min-width: 150px;
}
.file-type-option:hover {
border-color: #3b82f6;
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
}
.file-type-option.selected {
border-color: #3b82f6;
background-color: #eff6ff;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.file-type-option input[type="radio"] {
width: 16px;
height: 16px;
margin-left: 8px;
accent-color: #3b82f6;
}
.file-type-option label {
cursor: pointer;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.file-type-option i {
font-size: 1.1rem;
color: #6b7280;
}
.file-type-option.selected i {
color: #3b82f6;
}
/* تنسيقات حالة التصحيح */
.correction-status {
background-color: #fef3c7;
border: 1px solid #fcd34d;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
display: none;
}
.correction-status.active {
display: block;
animation: fadeIn 0.5s ease-in-out;
}
.correction-status h4 {
color: #92400e;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 8px;
}
.correction-status p {
color: #78350f;
margin-bottom: 0.5rem;
}
.correction-progress {
background-color: #fbbf24;
height: 4px;
border-radius: 2px;
overflow: hidden;
margin-top: 0.5rem;
}
.correction-progress-bar {
background-color: #f59e0b;
height: 100%;
width: 0%;
transition: width 0.3s ease;
}
/* تنسيقات معاينة التصحيح */
.correction-preview {
background-color: #f0fdf4;
border: 1px solid #86efac;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
display: none;
}
.correction-preview.active {
display: block;
animation: fadeIn 0.5s ease-in-out;
}
.correction-preview h4 {
color: #166534;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 8px;
}
.correction-changes {
background-color: #fff;
border: 1px solid #d1d5db;
border-radius: 6px;
padding: 0.75rem;
max-height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 0.9rem;
line-height: 1.4;
}
.corrected-text {
background-color: #dcfce7;
color: #166534;
padding: 2px 4px;
border-radius: 3px;
font-weight: 500;
}
.original-text {
background-color: #fee2e2;
color: #b91c1c;
padding: 2px 4px;
border-radius: 3px;
text-decoration: line-through;
}
/* ================================
تنسيقات جديدة لمؤشر التحميل
================================= */
.async-loading-indicator {
display: none;
padding: 15px;
border-radius: 10px;
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
text-align: center;
min-width: 250px;
}
.async-loading-indicator.active {
display: block;
animation: fadeIn 0.3s;
}
.async-spinner {
display: inline-block;
width: 50px;
height: 50px;
border: 5px solid #e5e7eb;
border-radius: 50%;
border-top-color: #3b82f6;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* تنسيقات محسنة للنافذة المنبثقة */
.enhanced-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9);
max-width: 600px;
width: 90%;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
z-index: 1100;
opacity: 0;
visibility: hidden;
overflow: hidden;
transition: all 0.3s;
}
.enhanced-popup.show {
opacity: 1;
visibility: visible;
transform: translate(-50%, -50%) scale(1);
}
.enhanced-popup-header {
background: linear-gradient(135deg, #3b82f6, #1e40af);
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.enhanced-popup-title {
font-size: 1.25rem;
font-weight: bold;
display: flex;
align-items: center;
}
.enhanced-popup-title i {
margin-left: 10px;
font-size: 1.5rem;
}
.enhanced-popup-close {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
transition: transform 0.2s;
}
.enhanced-popup-close:hover {
transform: scale(1.2);
}
.enhanced-popup-body {
padding: 25px;
max-height: 70vh;
overflow-y: auto;
}
.enhanced-popup-footer {
padding: 15px 25px;
background-color: #f8fafc;
border-top: 1px solid #e5e7eb;
display: flex;
justify-content: flex-end;
}
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1099;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
}
.popup-overlay.show {
opacity: 1;
visibility: visible;
}
.error-illustration {
max-width: 100%;
margin: 15px 0;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
}
.error-example {
background-color: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 15px;
margin: 15px 0;
font-family: 'Courier New', monospace;
}
.correct-example {
background-color: #f0fdf4;
border: 1px solid #86efac;
border-radius: 8px;
padding: 15px;
margin: 15px 0;
font-family: 'Courier New', monospace;
}
/* ================================
1. تنسيقات القوالب الرسمية (جديد)
================================= */
.template-selector {
margin-top: 1rem;
display: none;
animation: fadeIn 0.3s ease-in-out;
}
.template-selector.active {
display: block;
}
.template-selector select {
width: 100%;
padding: 0.75rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
background-color: white;
font-size: 1rem;
color: #374151;
transition: all 0.3s ease;
}
.template-selector select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.template-selector select option {
padding: 10px;
}
.template-info {
margin-top: 0.5rem;
padding: 0.75rem;
background-color: #eff6ff;
border-radius: 6px;
font-size: 0.9rem;
color: #1e40af;
}
/* ================================
2. تنسيقات إخفاء الشرح/المسودات (جديد)
================================= */
.settings-btn {
position: fixed;
bottom: 20px;
left: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #3b82f6;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
z-index: 999;
}
.settings-btn:hover {
transform: scale(1.1);
background: #2563eb;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.25);
}
.settings-panel {
position: fixed;
bottom: 80px;
left: 20px;
width: 280px;
background-color: white;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
padding: 16px;
z-index: 998;
transform: translateY(20px);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.settings-panel.active {
transform: translateY(0);
opacity: 1;
visibility: visible;
}
.settings-header {
display: flex;
align-items: center;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
}
.settings-header i {
margin-left: 8px;
color: #3b82f6;
font-size: 1.2rem;
}
.settings-option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f3f4f6;
}
.settings-option:last-child {
border-bottom: none;
}
.switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #e5e7eb;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #3b82f6;
}
input:focus + .slider {
box-shadow: 0 0 1px #3b82f6;
}
input:checked + .slider:before {
transform: translateX(20px);
}
/* ================================
3. تنسيقات زر إعادة تحليل الفقرة (جديد)
================================= */
.reanalyze-btn {
width: 28px;
height: 28px;
border-radius: 50%;
background-color: #f3f4f6;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
margin-right: 8px;
color: #4b5563;
}
.reanalyze-btn:hover {
background-color: #3b82f6;
color: white;
transform: rotate(180deg);
}
.reanalyze-btn.spin {
animation: spin 1s linear infinite;
background-color: #3b82f6;
color: white;
}
/* ================================
4. تنسيقات نمط التركيز (جديد)
================================= */
.focus-mode-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #f3f4f6;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
margin-right: auto;
}
.focus-mode-btn:hover {
background-color: #e5e7eb;
}
.focus-mode-btn.active {
background-color: #3b82f6;
color: white;
}
.focus-mode-btn i {
font-size: 1.1rem;
}
/* عندما يكون وضع التركيز نشطًا */
body.focus-on .error-filters,
body.focus-on .view-selector,
body.focus-on .analysis-summary,
body.focus-on #toggleDraftBtn,
body.focus-on #downloadExcelBtn,
body.focus-on #downloadWordBtn,
body.focus-on .segment-notes {
display: none !important;
}
body.focus-on .segment-comparison {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transform: scale(1.01);
transition: all 0.5s ease;
}
body.focus-on .highlight-number,
body.focus-on .highlight-missing,
body.focus-on .highlight-meaning {
animation: highlight-pulse 1.5s ease-in-out infinite;
}
/* ================================
5. تنسيقات خريطة الحرارة (جديد)
================================= */
.heat-bar {
position: fixed;
right: 10px;
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 70vh;
background-color: #f3f4f6;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
z-index: 990;
}
.heat-dot {
position: absolute;
width: 8px;
height: 4px;
border-radius: 2px;
right: 0;
transform: translateY(-50%);
cursor: pointer;
transition: all 0.3s ease;
}
.heat-dot:hover {
width: 12px;
height: 6px;
right: -2px;
}
.heat-dot.number-error {
background-color: #f59e0b;
}
.heat-dot.missing-error {
background-color: #3b82f6;
}
.heat-dot.meaning-error {
background-color: #ef4444;
}
/* Tooltip for heat dots */
.heat-dot::before {
content: attr(data-tooltip);
position: absolute;
top: 50%;
right: 16px;
transform: translateY(-50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 991;
}
.heat-dot::after {
content: "";
position: absolute;
top: 50%;
right: 12px;
transform: translateY(-50%);
border: 4px solid transparent;
border-left: 4px solid rgba(0, 0, 0, 0.8);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 991;
}
.heat-dot:hover::before,
.heat-dot:hover::after {
opacity: 1;
visibility: visible;
}
</style>
</head>
<body class="bg-gray-50">
<div class="min-h-screen pb-12">
<!-- ============ رأس الصفحة محّسن ============ -->
<header class="bg-gradient-to-r from-blue-600 to-indigo-800 text-white py-10 mb-6 shadow-xl">
<div class="max-w-6xl mx-auto px-4">
<!-- شعار واسم الشركة - محسّن -->
<div class="flex items-center justify-center mb-6">
<div class="logo-container flex items-center">
<div class="logo pulse-animation"></div>
<div class="company-name text-2xl font-bold">شركة الريحان</div>
</div>
</div>
<h1 class="text-4xl sm:text-5xl font-bold text-center mb-4 animate-scale">المراجع الذكي</h1>
<p class="text-center text-xl text-blue-100 opacity-90">نظام متكامل لمقارنة وتحليل النصوص المترجمة</p>
<!-- 4. زر وضع التركيز - جديد -->
<div class="flex justify-center mt-4">
<button id="focusModeBtn" class="focus-mode-btn">
<i class="fas fa-compress-alt"></i>
<span>وضع التركيز</span>
</button>
</div>
</div>
</header>
<!-- ============ المحتوى الرئيسي ============ -->
<main class="max-w-6xl mx-auto px-4">
<!-- قسم رفع الملفات - محسن -->
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-6 card-hover">
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3">
<i class="fas fa-file-upload text-blue-600 ml-2"></i> تحميل الملفات
</h2>
<!-- قسم اختيار نوع الملف - جديد -->
<div class="file-type-selector">
<h3 class="text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-cog text-blue-600 ml-2"></i> نوع الملف المُحمّل
</h3>
<p class="text-sm text-gray-600 mb-3">اختر نوع الملف لتحديد طريقة المعالجة المناسبة</p>
<div class="file-type-options">
<div class="file-type-option selected" data-type="normal">
<input type="radio" id="normalFile" name="fileType" value="normal" checked>
<label for="normalFile">
<i class="fas fa-file-alt"></i>
ملف عادي
</label>
</div>
<div class="file-type-option" data-type="official">
<input type="radio" id="officialFile" name="fileType" value="official">
<label for="officialFile">
<i class="fas fa-certificate"></i>
مستند رسمي
</label>
</div>
</div>
<div class="mt-3 p-3 bg-blue-50 rounded-lg">
<p class="text-sm text-blue-800" id="fileTypeDescription">
<i class="fas fa-info-circle ml-1"></i>
الملف العادي سيتم تحليله مباشرة بدون تصحيح مسبق
</p>
</div>
<!-- 1. قائمة القوالب الرسمية - جديد -->
<div id="docTemplateContainer" class="template-selector">
<label for="docTemplate" class="block text-sm font-bold text-gray-700 mb-2">اختر نوع المستند الرسمي:</label>
<select id="docTemplate" class="form-select">
<option value="birth">شهادة ميلاد</option>
<option value="id">بطاقة شخصية</option>
<option value="passport">جواز سفر</option>
<option value="driving">رخصة قيادة</option>
<option value="other">غير ذلك...</option>
</select>
<div class="template-info" id="templateInfo">
سيتم تطبيق قواعد التصحيح الخاصة بشهادات الميلاد
</div>
</div>
</div>
<!-- حالة التصحيح - جديد -->
<div id="correctionStatus" class="correction-status">
<h4>
<i class="fas fa-magic"></i>
جاري تصحيح النص الرسمي...
</h4>
<p id="correctionMessage">جاري تحديد الدولة وتطبيق قواعد التصحيح المناسبة...</p>
<div class="correction-progress">
<div id="correctionProgressBar" class="correction-progress-bar"></div>
</div>
</div>
<!-- معاينة التصحيح - جديد -->
<div id="correctionPreview" class="correction-preview">
<h4>
<i class="fas fa-check-circle"></i>
معاينة التصحيحات المطبقة
</h4>
<div id="correctionChanges" class="correction-changes">
<!-- سيتم عرض التغييرات هنا -->
</div>
<div class="mt-3 flex justify-between items-center">
<span class="text-sm text-green-600">
<i class="fas fa-info-circle ml-1"></i>
تم تصحيح الثوابت مع الحفاظ على تنسيق النص الأصلي
</span>
<button id="proceedWithCorrectedText" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg text-sm">
متابعة بالنص المُصحح
</button>
</div>
</div>
<!-- إحصائيات OCR -->
<div class="stats-container mb-4">
<div class="stats-badge">
عدد الصفحات المعالجة: <span id="ocrCounter">0</span>
</div>
<div class="stats-badge">
تاريخ آخر معالجة: <span id="lastOcrDate">-</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- ملف السورس -->
<div class="text-center">
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100">
<label class="cursor-pointer block">
<input type="file" id="sourceFile" accept=".docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls" class="hidden">
<i class="fas fa-file-upload text-5xl text-blue-500 mb-4"></i>
<span class="text-lg text-blue-600 group-hover:text-blue-700">ملف السورس</span>
</label>
</div>
</div>
<!-- ملف التارجت -->
<div class="text-center">
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100">
<label class="cursor-pointer block">
<input type="file" id="targetFile" accept=".docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls" class="hidden">
<i class="fas fa-file-download text-5xl text-blue-500 mb-4"></i>
<span class="text-lg text-blue-600 group-hover:text-blue-700">ملف التارجت</span>
</label>
</div>
</div>
</div>
<!-- حالة المعالجة -->
<div id="processingStatus" class="hidden mt-4">
<div class="alert alert-info bg-blue-50 border border-blue-200 rounded-xl p-4 my-4">
<div class="flex items-center">
<div class="animate-spin h-6 w-6 border-4 border-blue-600 rounded-full border-t-transparent ml-3"></div>
<span id="statusText">جاري معالجة الملف...</span>
</div>
</div>
<div class="progress mt-2">
<div id="progressBar" class="progress-bar" style="width: 0%"></div>
</div>
</div>
<!-- عرض صفحات PDF -->
<div id="pdfPagesCard" class="hidden mt-6">
<h3 class="text-xl font-bold mb-4 text-gray-800">صفحات الملف</h3>
<p>اختر الصفحات التي تريد معالجتها (انقر للتحديد)</p>
<div class="mb-3">
<button id="selectAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-3 rounded text-sm ml-2">تحديد الكل</button>
<button id="deselectAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-3 rounded text-sm">إلغاء تحديد الكل</button>
</div>
<div id="pdfPagesContainer" class="pdf-grid"></div>
<div class="mt-4 flex items-center space-x-2">
<div class="edit-toolbar flex flex-wrap gap-2">
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="rotateLeft">
<i class="fas fa-undo ml-1"></i> تدوير لليسار
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="rotateRight">
<i class="fas fa-redo ml-1"></i> تدوير لليمين
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="flipHorizontal">
<i class="fas fa-arrows-alt-h ml-1"></i> قلب أفقي
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="flipVertical">
<i class="fas fa-arrows-alt-v ml-1"></i> قلب عمودي
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="cropImage">
<i class="fas fa-crop ml-1"></i> قص الصورة
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="resetImage">
<i class="fas fa-sync-alt ml-1"></i> إعادة ضبط
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="improveContrast">
<i class="fas fa-adjust ml-1"></i> تحسين التباين
</button>
</div>
</div>
<div class="mt-3">
<button id="extractTextBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
<i class="fas fa-magic ml-2"></i> استخراج النص
</button>
</div>
<!-- محرر الصور -->
<div id="imageEditor" class="hidden mt-4">
<div class="border border-gray-300 rounded-lg bg-gray-100 p-2">
<canvas id="imageCanvas" class="max-w-full"></canvas>
</div>
</div>
</div>
<!-- عرض النتائج -->
<div id="resultsCard" class="hidden mt-6">
<h3 class="text-xl font-bold mb-4 text-gray-800">النص المستخرج</h3>
<div id="resultPreview"></div>
<div id="resultTextContainer" class="mt-4">
<div class="flex justify-between items-center mb-2">
<h4 class="font-bold text-gray-800">النص:</h4>
<button id="copyTextBtn" class="bg-blue-100 hover:bg-blue-200 text-blue-800 px-3 py-1 rounded-lg text-sm">
<i class="far fa-copy ml-1"></i> نسخ النص
</button>
</div>
<div id="resultText" class="result-text">
لم يتم استخراج نص بعد.
</div>
</div>
<div class="mt-3 flex space-x-2">
<button id="useAsSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
استخدام النص كنص مصدر
</button>
<button id="useAsTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
استخدام النص كنص هدف
</button>
<button id="downloadTextBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition-all">
تنزيل النص
</button>
</div>
</div>
<!-- عرض ملفات Excel -->
<div id="excelPreviewCard" class="hidden mt-6">
<h3 class="text-xl font-bold mb-4 text-gray-800">محتوى ملف Excel</h3>
<div class="excel-controls" id="sheetSelectorContainer">
<!-- سيتم إضافة أزرار لاختيار الأوراق ديناميكيًا -->
</div>
<div class="excel-preview" id="excelContent">
<!-- سيتم عرض جدول Excel هنا -->
</div>
<div class="mt-3 flex space-x-2">
<button id="useExcelAsSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
استخدام المحتوى كنص مصدر
</button>
<button id="useExcelAsTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
استخدام المحتوى كنص هدف
</button>
</div>
</div>
</div>
<!-- قسم إدخال النصوص يدويًا - محسن -->
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-6 card-hover">
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3">
<i class="fas fa-pen-alt text-blue-600 ml-2"></i> إدخال النصوص
</h2>
<div class="space-y-6">
<!-- النص المصدر -->
<div class="group">
<label class="block text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-language text-blue-600 ml-2"></i> النص المصدر
</label>
<textarea id="sourceText" dir="rtl" class="w-full px-6 py-4 border-2 border-gray-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب النص المصدر هنا..."></textarea>
</div>
<!-- النص الهدف -->
<div class="group">
<label class="block text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-language text-blue-600 ml-2"></i> النص الهدف
</label>
<textarea id="targetText" dir="ltr" class="w-full px-6 py-4 border-2 border-gray-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب النص الهدف هنا..."></textarea>
</div>
<!-- خيار استخدام قاعدة المصطلحات -->
<div class="mt-4 flex items-center bg-blue-50 p-3 rounded-lg">
<input type="checkbox" id="terminologyCheck" class="ml-2 w-5 h-5 text-blue-600 rounded">
<label for="terminologyCheck" class="text-lg text-gray-700">استخدام قاعدة المصطلحات</label>
<div class="mr-auto">
<span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">ميزة إضافية</span>
</div>
</div>
</div>
</div>
<!-- قسم المصادر الإضافية - محسن -->
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-6 card-hover">
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3">
<i class="fas fa-book-open text-blue-600 ml-2"></i> مصادر إضافية
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- رفع ملف المصادر الإضافية -->
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100">
<label class="cursor-pointer block">
<input type="file" id="sourceExtraFile" accept=".docx,.pdf,.xlsx,.xls" class="hidden">
<i class="fas fa-upload text-5xl text-blue-500 mb-4"></i>
<span class="text-lg text-blue-600 group-hover:text-blue-700">تحميل ملف المصدر</span>
</label>
</div>
<!-- إدخال المصادر يدويًا -->
<div class="group">
<label class="block text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-edit text-blue-600 ml-2"></i> إدخال المصادر يدويًا
</label>
<textarea id="sourceExtraText" dir="rtl" class="w-full px-6 py-4 border-2 border-blue-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب المصادر هنا..."></textarea>
</div>
</div>
</div>
<!-- زر التحليل - محسن -->
<button id="submitBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-5 px-8 rounded-xl transition-all transform hover:scale-105 focus:ring-blue-200 text-xl shadow-lg hover:shadow-xl mb-8 pulse-animation">
<div class="flex items-center justify-center">
<i class="fas fa-sync-alt ml-2"></i> تحليل النصوص
</div>
</button>
<!-- قسم نتائج التحليل - محسن مع أزرار اختيار طريقة العرض المنفصلة -->
<div id="resultSection" class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-8 hidden card-hover">
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-3 flex items-center">
<i class="fas fa-search text-blue-600 ml-2"></i> نتائج التحليل والمقارنة
</h2>
<div id="errorsList" class="space-y-3 mb-6"></div>
<!-- فلتر الأخطاء - إضافة جديدة -->
<div class="bg-gray-50 p-4 rounded-xl mb-4">
<h3 class="text-lg font-bold mb-3 text-gray-700 flex items-center">
<i class="fas fa-filter text-blue-500 ml-2"></i> فلترة الأخطاء حسب النوع
</h3>
<div class="error-filters">
<div class="error-filter filter-all active" data-filter="all">
<i class="fas fa-layer-group"></i> جميع الأخطاء
</div>
<div class="error-filter filter-numbers" data-filter="number">
<i class="fas fa-hashtag"></i> أخطاء الأرقام
</div>
<div class="error-filter filter-missing" data-filter="missing">
<i class="fas fa-minus-circle"></i> النصوص المفقودة
</div>
<div class="error-filter filter-meaning" data-filter="meaning">
<i class="fas fa-exclamation-circle"></i> اختلافات المعنى
</div>
</div>
</div>
<!-- أزرار اختيار طريقة العرض - محسنة -->
<div class="view-selector mb-4">
<div class="view-btn active" data-view="segmentView">
<i class="fas fa-th-large"></i> العرض المقسم
</div>
<div class="view-btn" data-view="interactiveView">
<i class="fas fa-exchange-alt"></i> العرض التفاعلي
</div>
</div>
<!-- 2. طريقة العرض المقسمة - محسنة ونشطة افتراضيًا -->
<div id="segmentView" class="view-container active">
<div id="segmentedComparisonContainer" class="space-y-4">
<!-- سيتم إنشاء المقاطع هنا بواسطة JavaScript -->
</div>
<!-- قسم شرح الأخطاء للعرض المقسم - محسن -->
<div class="analysis-summary mt-6">
<div class="analysis-summary-header">
<i class="fas fa-clipboard-check"></i> شرح الأخطاء في المقاطع
</div>
<div class="analysis-summary-body" id="segmentViewExplanation">
<!-- سيتم تعبئته بواسطة JavaScript -->
</div>
</div>
</div>
<!-- 3. طريقة العرض التفاعلية - محسنة -->
<div id="interactiveView" class="view-container">
<div class="bg-blue-50 rounded-xl p-6 mb-4">
<div class="flex flex-wrap justify-between items-center mb-4">
<h4 class="text-lg font-bold text-blue-800 flex items-center">
<i class="fas fa-exchange-alt ml-2"></i> عرض تفاعلي للاختلافات
</h4>
<div class="flex space-x-2 mt-2 sm:mt-0">
<button id="prevDiff" class="bg-blue-200 hover:bg-blue-300 text-blue-800 px-3 py-1 rounded-lg disabled:opacity-50 transition-all">
<i class="fas fa-arrow-right ml-1"></i> السابق
</button>
<span id="diffCounter" class="bg-white px-3 py-1 rounded-lg">0/0</span>
<button id="nextDiff" class="bg-blue-200 hover:bg-blue-300 text-blue-800 px-3 py-1 rounded-lg disabled:opacity-50 transition-all">
<i class="fas fa-arrow-left ml-1"></i> التالي
</button>
</div>
</div>
<div id="currentDiffDisplay" class="bg-white rounded-lg p-4 min-h-[100px] border border-blue-200">
<p class="text-gray-500 text-center">اضغط "التالي" للبدء في استعراض الاختلافات</p>
</div>
<!-- إضافة عرض المصدر والهدف معًا -->
<div class="diff-source-target hidden" id="diffDetailedView">
<div class="diff-panel diff-source">
<div class="diff-panel-header">
<i class="fas fa-file-alt"></i> النص في المصدر
</div>
<div id="diffSourceText"></div>
</div>
<div class="diff-panel diff-target">
<div class="diff-panel-header">
<i class="fas fa-file-alt"></i> النص في الهدف
</div>
<div id="diffTargetText"></div>
</div>
</div>
<!-- إضافة مرجع الاختلاف -->
<div class="diff-reference hidden" id="diffReference">
<div class="reference-header">
<i class="fas fa-info-circle"></i> مرجع الاختلاف
</div>
<div id="diffReferenceText"></div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-blue-50 rounded-xl p-4 border border-blue-100">
<h5 class="font-bold text-blue-800 mb-2 flex items-center">
<i class="fas fa-chart-pie ml-2"></i> ملخص الاختلافات
</h5>
<div id="diffSummary" class="space-y-2">
<div class="flex items-center justify-between bg-white p-2 rounded">
<span>اختلافات الأرقام:</span>
<span id="numberDiffCount" class="bg-yellow-100 px-2 py-1 rounded font-bold">0</span>
</div>
<div class="flex items-center justify-between bg-white p-2 rounded">
<span>النصوص المفقودة:</span>
<span id="missingTextCount" class="bg-blue-100 px-2 py-1 rounded font-bold">0</span>
</div>
<div class="flex items-center justify-between bg-white p-2 rounded">
<span>اختلافات المعنى:</span>
<span id="meaningDiffCount" class="bg-red-100 px-2 py-1 rounded font-bold">0</span>
</div>
</div>
</div>
<div class="bg-green-50 rounded-xl p-4 border border-green-100">
<h5 class="font-bold text-green-800 mb-2 flex items-center">
<i class="fas fa-lightbulb ml-2"></i> توصيات المعالجة
</h5>
<div id="diffRecommendations" class="space-y-2 text-green-800">
<!-- سيتم إضافة التوصيات هنا عن طريق JavaScript -->
</div>
</div>
</div>
<!-- قسم توضيحي للعرض التفاعلي -->
<div class="analysis-summary mt-6">
<div class="analysis-summary-header">
<i class="fas fa-clipboard-check"></i> التفاصيل والتوضيحات
</div>
<div class="analysis-summary-body" id="interactiveViewExplanation">
<!-- سيتم تعبئته بواسطة JavaScript -->
</div>
</div>
</div>
</div>
<!-- مؤشر التحميل غير المتزامن الجديد -->
<div id="asyncLoadingIndicator" class="async-loading-indicator">
<div class="async-spinner"></div>
<div id="asyncLoadingText" class="font-bold text-gray-700 mt-2">جاري معالجة البيانات...</div>
<div id="asyncLoadingProgress" class="text-sm text-gray-500 mt-1">0%</div>
</div>
<!-- نافذة منبثقة محسنة لشرح الأخطاء -->
<div id="popup-overlay" class="popup-overlay"></div>
<div id="enhancedErrorPopup" class="enhanced-popup">
<div class="enhanced-popup-header">
<div class="enhanced-popup-title">
<i class="fas fa-exclamation-circle"></i>
<span id="enhancedPopupTitle">تفاصيل الخطأ</span>
</div>
<button class="enhanced-popup-close">&times;</button>
</div>
<div id="enhancedPopupContent" class="enhanced-popup-body">
<!-- سيتم إضافة محتوى شرح الخطأ هنا بواسطة JavaScript -->
</div>
<div class="enhanced-popup-footer">
<button id="closeEnhancedPopup" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition-all">
فهمت
</button>
</div>
</div>
<!-- قسم المسودة (تقسيمات الفقرات) - محسن ومخفي بشكل افتراضي -->
<div id="fullTextDraftSection" class="bg-white rounded-xl shadow-md p-6 border border-gray-100 transition-all animate-scale mb-8 hidden card-hover">
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-3 flex items-center">
<i class="fas fa-file-alt text-blue-600 ml-2"></i> مسودة التحليل النصي
<span class="draft-marker mr-2">مسودة</span>
</h2>
<div id="paragraphDivisionsContainer" class="mt-4 space-y-2">
<!-- سيتم إنشاء التقسيمات هنا بواسطة JavaScript -->
</div>
</div>
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-8 space-y-4 md:space-y-0">
<button id="toggleDraftBtn" class="w-full md:w-auto bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
<i class="fas fa-eye ml-2"></i> عرض/إخفاء مسودة التحليل
</button>
<!-- أزرار تنزيل التقرير -->
<div class="flex space-x-4">
<button id="downloadExcelBtn" class="w-full md:w-auto bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition-all">
<i class="fas fa-file-excel ml-2"></i> تنزيل التقرير (Excel)
</button>
<button id="downloadWordBtn" class="w-full md:w-auto bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
<i class="fas fa-file-word ml-2"></i> تنزيل التقرير (Word)
</button>
</div>
</div>
<!-- 5. خريطة الحرارة - جديد -->
<div id="heatBar" class="heat-bar">
<!-- نقاط الحرارة ستضاف هنا من خلال JavaScript -->
</div>
<!-- حقوق الملكية - إضافة جديدة -->
<footer class="copyright">
<p>جميع الحقوق محفوظة لشركة فاست برو للبرمجيات والذكاء الاصطناعي &copy; <span id="currentYear"></span></p>
<script>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
</footer>
</main>
</div>
<!-- 2. زر الإعدادات ولوحة التحكم - جديد -->
<div id="settingsBtn" class="settings-btn">
<i class="fas fa-cog"></i>
</div>
<div id="settingsPanel" class="settings-panel">
<div class="settings-header">
<i class="fas fa-cog"></i>
<span class="font-bold">إعدادات العرض</span>
</div>
<div class="settings-option">
<span>مسودة التحليل</span>
<label class="switch">
<input type="checkbox" id="switchDraft" checked>
<span class="slider"></span>
</label>
</div>
<div class="settings-option">
<span>شروحات الأخطاء</span>
<label class="switch">
<input type="checkbox" id="switchExplain" checked>
<span class="slider"></span>
</label>
</div>
</div>
<!-- ================================
جافا سكريبت: الوظائف والمعالجة
================================= -->
<script>
// تعريف متغيرات PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
// إعدادات API - تم تحديثها من النموذج الصغير الناجح
const RAPIDAPI_KEY = '32769fb369mshfdf6f5e28e26674p1f3764jsn2a31085a1fc7';
const OCR_API_URL = 'https://ocr43.p.rapidapi.com/v1/results';
const DEEPSEEK_API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01';
const DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions';
// متغيرات عامة للتحليل
let fullAnalysisText = ''; // متغير لتخزين النص الكامل للتحليل
let analysisSegments = []; // متغير لتخزين مقاطع التحليل المقسمة
let allDifferences = []; // متغير لتخزين جميع الاختلافات للعرض التفاعلي
let currentDiffIndex = -1; // مؤشر للاختلاف الحالي
// متغيرات OCR
let ocrPagesCount = 0; // عداد لعدد الصفحات المعالجة
let documentPages = []; // مصفوفة لتخزين صفحات المستند
let selectedPages = []; // مصفوفة لتخزين الصفحات المحددة
let extractedTexts = []; // مصفوفة لتخزين النصوص المستخرجة
let extractedPageNumbers = []; // مصفوفة لتخزين أرقام الصفحات المستخرجة
let currentProcessingMode = ''; // لتحديد إذا كانت المعالجة للمصدر أو الهدف
// متغيرات محرر الصور
let canvas = null;
let fabricCanvas = null;
let originalImageData = null;
let isInCropMode = false;
let cropRect = null;
let currentPageIndex = 0;
// متغيرات جديدة للتحليل المتقدم
let sentenceErrors = []; // تخزين الأخطاء على مستوى الجملة
let errorExplanations = {}; // تخزين شروحات الأخطاء
// متغير للفلتر الحالي
let currentErrorFilter = 'all';
// متغيرات Excel
let excelWorkbook = null;
let currentSheetName = '';
let excelData = null;
// متغيرات جديدة لتصحيح المستندات الرسمية
let selectedFileType = 'normal'; // افتراضي: ملف عادي
let detectedCountry = null; // الدولة المكتشفة
let correctionApplied = false; // تم تطبيق التصحيح أم لا
let originalTextBeforeCorrection = ''; // النص الأصلي قبل التصحيح
let correctedText = ''; // النص بعد التصحيح
let correctionChanges = []; // التغييرات المطبقة
// متغيرات جديدة للإعدادات والأوضاع
let showDraft = true; // متغير لعرض/إخفاء مسودة التحليل
let showExplanations = true; // متغير لعرض/إخفاء شروحات الأخطاء
let focusModeActive = false; // متغير لتفعيل/إلغاء وضع التركيز
/* =====================================
تهيئة الصفحة وتحميل الإعدادات المحفوظة
===================================== */
document.addEventListener('DOMContentLoaded', function() {
// استرداد عداد OCR وتاريخ آخر معالجة من localStorage
const savedCount = localStorage.getItem('ocrPagesCount');
const lastDate = localStorage.getItem('lastOcrDate');
// استرداد إعدادات العرض من localStorage
const savedShowDraft = localStorage.getItem('showDraft');
const savedShowExplain = localStorage.getItem('showExplain');
if (savedCount) {
ocrPagesCount = parseInt(savedCount);
document.getElementById('ocrCounter').textContent = ocrPagesCount;
}
if (lastDate) {
document.getElementById('lastOcrDate').textContent = lastDate;
}
// تحميل إعدادات العرض
if (savedShowDraft !== null) {
showDraft = savedShowDraft === '1';
document.getElementById('switchDraft').checked = showDraft;
}
if (savedShowExplain !== null) {
showExplanations = savedShowExplain === '1';
document.getElementById('switchExplain').checked = showExplanations;
}
// تهيئة النافذة المنبثقة المحسنة
initEnhancedPopup();
// تهيئة اختيار نوع الملف
initFileTypeSelector();
// تهيئة زر الإعدادات ولوحة التحكم
initSettingsPanel();
// تهيئة وضع التركيز
initFocusMode();
// إزالة العرض الكلاسيكي وتعيين العرض المقسم كافتراضي
setupViewModes();
// تهيئة خريطة الحرارة
initHeatMap();
// إضافة أحداث لأزرار عرض PDF
document.getElementById('selectAllBtn')?.addEventListener('click', selectAllPages);
document.getElementById('deselectAllBtn')?.addEventListener('click', deselectAllPages);
document.getElementById('extractTextBtn')?.addEventListener('click', extractText);
// إضافة أحداث لأزرار النتائج
document.getElementById('copyTextBtn')?.addEventListener('click', copyText);
document.getElementById('downloadTextBtn')?.addEventListener('click', downloadText);
document.getElementById('useAsSourceBtn')?.addEventListener('click', function() {
useOcrText('source');
});
document.getElementById('useAsTargetBtn')?.addEventListener('click', function() {
useOcrText('target');
});
// إضافة أحداث لأزرار Excel
document.getElementById('useExcelAsSourceBtn')?.addEventListener('click', function() {
useExcelContent('source');
});
document.getElementById('useExcelAsTargetBtn')?.addEventListener('click', function() {
useExcelContent('target');
});
// إضافة أحداث لأزرار تحرير الصور
document.getElementById('rotateLeft')?.addEventListener('click', rotateImageLeft);
document.getElementById('rotateRight')?.addEventListener('click', rotateImageRight);
document.getElementById('flipHorizontal')?.addEventListener('click', flipImageHorizontal);
document.getElementById('flipVertical')?.addEventListener('click', flipImageVertical);
document.getElementById('cropImage')?.addEventListener('click', function() {
if (isInCropMode) {
applyCrop();
} else {
activateCropMode();
}
});
document.getElementById('resetImage')?.addEventListener('click', resetImage);
document.getElementById('improveContrast')?.addEventListener('click', improveContrast);
// إضافة حدث للمتابعة بالنص المُصحح
document.getElementById('proceedWithCorrectedText')?.addEventListener('click', function() {
// إخفاء معاينة التصحيح
document.getElementById('correctionPreview').classList.remove('active');
// استخدام النص المُصحح في المكان المناسب
if (currentProcessingMode === 'source') {
document.getElementById('sourceText').value = correctedText;
addError('تم تطبيق التصحيحات على النص المصدر', 'info');
} else if (currentProcessingMode === 'target') {
document.getElementById('targetText').value = correctedText;
addError('تم تطبيق التصحيحات على النص الهدف', 'info');
} else if (currentProcessingMode === 'extra') {
document.getElementById('sourceExtraText').value = correctedText;
addError('تم تطبيق التصحيحات على المصدر الإضافي', 'info');
}
});
// إضافة حدث للقالب المحدد
document.getElementById('docTemplate')?.addEventListener('change', updateTemplateInfo);
// إضافة أحداث التلقائية لمعالجة الملفات
document.getElementById('sourceFile').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
processFile(file, 'source');
}
});
document.getElementById('targetFile').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
processFile(file, 'target');
}
});
document.getElementById('sourceExtraFile').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
processFile(file, 'extra');
}
});
// إضافة أحداث لأزرار طرق العرض المنفصلة
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', function() {
// إزالة الفئة active من جميع الأزرار والحاويات
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.view-container').forEach(c => c.classList.remove('active'));
// تفعيل الزر والحاوية المحددين
this.classList.add('active');
const viewId = this.getAttribute('data-view');
document.getElementById(viewId).classList.add('active');
});
});
// إضافة أحداث لأزرار فلتر الأخطاء
document.querySelectorAll('.error-filter').forEach(filter => {
filter.addEventListener('click', function() {
// إزالة الفئة active من جميع الأزرار
document.querySelectorAll('.error-filter').forEach(f => f.classList.remove('active'));
// تفعيل الزر المحدد
this.classList.add('active');
// تحديث الفلتر الحالي
currentErrorFilter = this.getAttribute('data-filter');
// تطبيق الفلتر على جميع طرق العرض
applyErrorFilter(currentErrorFilter);
// تحديث خريطة الحرارة بعد تطبيق الفلتر
updateHeatMap();
});
});
// إضافة حدث للتبديل بين عرض وإخفاء المسودة
document.getElementById('toggleDraftBtn').addEventListener('click', function() {
const draftSection = document.getElementById('fullTextDraftSection');
draftSection.classList.toggle('hidden');
// تغيير نص الزر
this.innerHTML = draftSection.classList.contains('hidden') ?
'<i class="fas fa-eye ml-2"></i> عرض مسودة التحليل' :
'<i class="fas fa-eye-slash ml-2"></i> إخفاء مسودة التحليل';
});
// إضافة حدث لتنزيل التقرير بصيغة Excel
document.getElementById('downloadExcelBtn').addEventListener('click', function() {
// إنشاء تقرير Excel بناء على نتائج التحليل
if (!analysisSegments || analysisSegments.length === 0) {
alert('لا توجد نتائج تحليل للتنزيل');
return;
}
try {
// إنشاء مصفوفة البيانات للتقرير
const reportData = [];
// إضافة رأس الجدول
reportData.push(['رقم المقطع', 'النص المصدر', 'النص الهدف', 'الأخطاء', 'التحليل']);
// إضافة بيانات التحليل
analysisSegments.forEach((segment, index) => {
const errors = [];
if (segment.errors.numbers > 0) errors.push(`اختلافات أرقام: ${segment.errors.numbers}`);
if (segment.errors.missing > 0) errors.push(`نصوص مفقودة: ${segment.errors.missing}`);
if (segment.errors.meaning > 0) errors.push(`اختلافات معنى: ${segment.errors.meaning}`);
reportData.push([
index + 1,
segment.source,
segment.target,
errors.join('\n'),
segment.analysis
]);
});
// إنشاء ورقة عمل
const ws = XLSX.utils.aoa_to_sheet(reportData);
// إنشاء المصنف
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "تقرير التحليل");
// تنزيل الملف
XLSX.writeFile(wb, "تقرير_تحليل_النصوص.xlsx");
} catch (error) {
console.error("خطأ في إنشاء ملف Excel:", error);
alert("حدث خطأ أثناء إنشاء التقرير");
}
});
// إضافة حدث لتنزيل التقرير بصيغة Word
document.getElementById('downloadWordBtn').addEventListener('click', function() {
// سيتم تنفيذ تنزيل التقرير بصيغة Word
downloadWordReport();
});
});
/* =====================================
1. تهيئة وتحديث معلومات القالب الرسمي - جديد
===================================== */
function updateTemplateInfo() {
const templateSelect = document.getElementById('docTemplate');
const templateInfo = document.getElementById('templateInfo');
if (!templateSelect || !templateInfo) return;
const selectedTemplate = templateSelect.value;
let infoText = '';
switch (selectedTemplate) {
case 'birth':
infoText = 'سيتم تطبيق قواعد التصحيح الخاصة بشهادات الميلاد';
break;
case 'id':
infoText = 'سيتم تطبيق قواعد التصحيح الخاصة ببطاقات الهوية الشخصية';
break;
case 'passport':
infoText = 'سيتم تطبيق قواعد التصحيح الخاصة بجوازات السفر';
break;
case 'driving':
infoText = 'سيتم تطبيق قواعد التصحيح الخاصة برخص القيادة';
break;
case 'other':
infoText = 'سيتم تطبيق قواعد التصحيح العامة للوثائق الرسمية';
break;
}
templateInfo.textContent = infoText;
}
/* =====================================
2. تهيئة لوحة الإعدادات - جديد
===================================== */
function initSettingsPanel() {
const settingsBtn = document.getElementById('settingsBtn');
const settingsPanel = document.getElementById('settingsPanel');
const switchDraft = document.getElementById('switchDraft');
const switchExplain = document.getElementById('switchExplain');
// عرض/إخفاء لوحة الإعدادات عند النقر على الزر
settingsBtn.addEventListener('click', function() {
settingsPanel.classList.toggle('active');
});
// إضافة الأحداث لمفاتيح التبديل
switchDraft.addEventListener('change', function() {
showDraft = this.checked;
localStorage.setItem('showDraft', showDraft ? '1' : '0');
// تطبيق التغييرات مباشرة
toggleDraftDisplay();
});
switchExplain.addEventListener('change', function() {
showExplanations = this.checked;
localStorage.setItem('showExplain', showExplanations ? '1' : '0');
// تطبيق التغييرات مباشرة
toggleExplanationsDisplay();
});
// إخفاء لوحة الإعدادات عند النقر خارجها
document.addEventListener('click', function(event) {
if (!settingsBtn.contains(event.target) && !settingsPanel.contains(event.target)) {
settingsPanel.classList.remove('active');
}
});
}
/* =====================================
2. وظائف عرض/إخفاء المسودة والشروحات - جديد
===================================== */
function toggleDraftDisplay() {
const draftSection = document.getElementById('fullTextDraftSection');
if (!draftSection) return;
if (showDraft) {
// استعادة العرض إذا كانت مرئية سابقًا
if (!draftSection.classList.contains('hidden-by-settings')) {
draftSection.classList.remove('hidden');
}
} else {
// حفظ الحالة الأصلية ثم إخفاء
if (!draftSection.classList.contains('hidden')) {
draftSection.classList.add('hidden-by-settings');
}
draftSection.classList.add('hidden');
}
// تحديث زر التبديل أيضًا
const toggleBtn = document.getElementById('toggleDraftBtn');
if (toggleBtn) {
if (!showDraft) {
toggleBtn.classList.add('hidden');
} else {
toggleBtn.classList.remove('hidden');
}
}
}
function toggleExplanationsDisplay() {
// إخفاء/إظهار أقسام شرح الأخطاء
const explanationSections = document.querySelectorAll('.analysis-summary');
explanationSections.forEach(section => {
if (showExplanations) {
section.classList.remove('hidden');
} else {
section.classList.add('hidden');
}
});
// إخفاء/إظهار التوضيحات في النصوص أيضًا
const explanationElements = document.querySelectorAll('.segment-notes');
explanationElements.forEach(element => {
if (showExplanations) {
element.classList.remove('hidden');
} else {
element.classList.add('hidden');
}
});
}
/* =====================================
3. دالة إعادة تحليل فقرة محددة - جديد
===================================== */
async function reanalyzeSegment(segmentIndex) {
if (!analysisSegments || !analysisSegments[segmentIndex]) {
console.error('لا يمكن إعادة تحليل المقطع: المقطع غير موجود');
return;
}
try {
// تحويل زر إعادة التحليل إلى حالة التحميل
const reanalyzeBtn = document.querySelector(`.segment-comparison[data-segment-index="${segmentIndex}"] .reanalyze-btn`);
if (reanalyzeBtn) {
reanalyzeBtn.classList.add('spin');
}
// استخراج النصوص المصدر والهدف للمقطع المحدد
const sourceText = analysisSegments[segmentIndex].source;
const targetText = analysisSegments[segmentIndex].target;
// إظهار مؤشر التحميل
showLoadingIndicator(`جاري إعادة تحليل المقطع ${segmentIndex + 1}...`, '0%');
// استدعاء التحليل للمقطع المحدد فقط
updateLoadingProgress(`جاري تحليل المقطع ${segmentIndex + 1}...`, '50%');
const analysisResult = await analyzeAlignedPair(sourceText, targetText, segmentIndex + 1);
// تحديث نتائج التحليل في واجهة المستخدم
updateSegmentAnalysis(segmentIndex, analysisResult);
// تحديث المقطع في العرض المقسم
updateSegmentDisplay(segmentIndex, analysisResult);
// تحديث متغير تخزين التحليل
analysisSegments[segmentIndex].analysis = analysisResult.analysis;
analysisSegments[segmentIndex].errors = analysisResult.errors;
// تحديث العرض التفاعلي وملخصات الأخطاء
updateAnalysisSummary();
// تحديث خريطة الحرارة
updateHeatMap();
// إخفاء مؤشر التحميل
updateLoadingProgress('تم إعادة التحليل بنجاح!', '100%');
setTimeout(() => {
hideLoadingIndicator();
// إعادة زر التحليل إلى حالته الطبيعية
if (reanalyzeBtn) {
reanalyzeBtn.classList.remove('spin');
}
// إظهار رسالة نجاح
addError(`تم إعادة تحليل المقطع ${segmentIndex + 1} بنجاح`, 'info');
}, 1000);
} catch (error) {
console.error('خطأ في إعادة تحليل المقطع:', error);
// إعادة زر التحليل إلى حالته الطبيعية
const reanalyzeBtn = document.querySelector(`.segment-comparison[data-segment-index="${segmentIndex}"] .reanalyze-btn`);
if (reanalyzeBtn) {
reanalyzeBtn.classList.remove('spin');
}
hideLoadingIndicator();
addError(`خطأ في إعادة تحليل المقطع: ${error.message}`, 'error');
}
}
// تحديث عرض المقطع بعد إعادة التحليل
function updateSegmentDisplay(segmentIndex, analysisResult) {
const segmentElement = document.querySelector(`.segment-comparison[data-segment-index="${segmentIndex}"]`);
if (!segmentElement) return;
// تحديث علامات الأخطاء
const tagsContainer = segmentElement.querySelector('.segment-header > div');
if (tagsContainer) {
let tagHTML = '';
if (analysisResult.errors.numbers > 0) {
tagHTML += '<span class="segment-tag tag-error">أخطاء أرقام</span>';
}
if (analysisResult.errors.missing > 0) {
tagHTML += '<span class="segment-tag tag-warning">نصوص مفقودة</span>';
}
if (analysisResult.errors.meaning > 0) {
tagHTML += '<span class="segment-tag tag-info">اختلاف معنى</span>';
}
if (analysisResult.errors.numbers === 0 &&
analysisResult.errors.missing === 0 &&
analysisResult.errors.meaning === 0) {
tagHTML = '<span class="segment-tag tag-success">مطابق</span>';
}
tagsContainer.innerHTML = tagHTML;
}
// تحديث محتوى التحليل
const notesContainer = segmentElement.querySelector('.segment-notes');
if (notesContainer) {
notesContainer.innerHTML = formatAnalysisText(analysisResult.analysis);
}
// تحديث المحتوى المصدر والهدف مع التمييز الجديد
const sourceContainer = segmentElement.querySelector('.segment-source');
const targetContainer = segmentElement.querySelector('.segment-target');
if (sourceContainer && targetContainer) {
const sourceHighlighted = applyHighlights(analysisResult.sourceText, analysisResult.targetText, analysisResult.analysis);
const targetHighlighted = applyHighlights(analysisResult.targetText, analysisResult.sourceText, analysisResult.analysis);
sourceContainer.innerHTML = sourceHighlighted;
targetContainer.innerHTML = targetHighlighted;
}
// إضافة مستمعي الأحداث للتحديدات الجديدة
setTimeout(() => {
segmentElement.querySelectorAll('.sentence-with-error').forEach(element => {
element.addEventListener('click', function() {
handleErrorClick(this);
});
});
}, 100);
}
/* =====================================
3. معالجة النقر على عناصر الخطأ - مساعد لإعادة التحليل
===================================== */
function handleErrorClick(element) {
// الحصول على النص الكامل للجملة
const sentenceText = element.textContent;
// تحديد نوع الخطأ من خلال الفئات داخل الجملة
let errorType = 'general';
let errorSpecificText = '';
// البحث عن الخطأ المحدد داخل الجملة
const numberHighlight = element.querySelector('.highlight-number');
const missingHighlight = element.querySelector('.highlight-missing');
const meaningHighlight = element.querySelector('.highlight-meaning');
if (numberHighlight) {
errorType = 'number';
errorSpecificText = numberHighlight.textContent;
} else if (missingHighlight) {
errorType = 'missing';
errorSpecificText = missingHighlight.textContent;
} else if (meaningHighlight) {
errorType = 'meaning';
errorSpecificText = meaningHighlight.textContent;
}
const sentenceNumber = element.getAttribute('data-sentence-number');
// إعداد أمثلة حسب نوع الخطأ
let examples = null;
if (errorType === 'number') {
examples = {
incorrect: `المادة <span class="highlight-number">٥</span> من القانون، تُطبق الغرامة المقدرة <span class="highlight-number">٤٥٠</span> دينارًا.`,
correct: `المادة <span class="corrected-text">٥</span> من القانون، تُطبق الغرامة المقدرة <span class="corrected-text">٤٥٠</span> دينارًا.`
};
} else if (errorType === 'missing') {
examples = {
incorrect: `اتفق الطرفان على أن يتم تسليم البضائع <span class="highlight-missing">خلال 30 يومًا من توقيع العقد</span>.`,
correct: `اتفق الطرفان على أن يتم تسليم البضائع <span class="corrected-text">خلال 30 يومًا من توقيع العقد</span>.`
};
} else if (errorType === 'meaning') {
examples = {
incorrect: `أقرت المحكمة <span class="highlight-meaning">بإدانة</span> المتهم.`,
correct: `أقرت المحكمة <span class="corrected-text">ببراءة</span> المتهم.`
};
}
// إعداد شرح أكثر ودية مع أمثلة
let explanation = '';
if (errorType === 'number') {
explanation = `يبدو أن هناك اختلافًا في الأرقام في هذه الجملة. الرقم "${errorSpecificText}" في النص المصدر لا يتطابق مع النص الهدف. قد يكون هناك خطأ في نقل الرقم أو تحويله بين أنظمة الأرقام المختلفة (العربية، الإنجليزية، الهندية).`;
} else if (errorType === 'missing') {
explanation = `هناك نص مفقود في الترجمة! النص "${errorSpecificText}" موجود في المصدر ولكن لم يتم ترجمته أو تضمينه في النص الهدف. هذا يؤدي إلى فقدان جزء من المعنى الأصلي.`;
} else if (errorType === 'meaning') {
explanation = `هناك اختلاف مهم في المعنى! النص "${errorSpecificText}" تمت ترجمته بطريقة تغير المعنى الأصلي. قد يكون هناك خطأ في فهم السياق أو اختيار المصطلحات المناسبة.`;
} else {
explanation = `هناك خطأ في هذه الجملة: "${sentenceText}"<br><br>يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`;
}
// استخدام النافذة المنبثقة المحسنة
showEnhancedPopup(errorType, sentenceText, explanation, examples);
}
/* =====================================
4. تهيئة وضع التركيز - جديد
===================================== */
function initFocusMode() {
const focusModeBtn = document.getElementById('focusModeBtn');
if (!focusModeBtn) return;
focusModeBtn.addEventListener('click', function() {
focusModeActive = !focusModeActive;
if (focusModeActive) {
document.body.classList.add('focus-on');
focusModeBtn.classList.add('active');
} else {
document.body.classList.remove('focus-on');
focusModeBtn.classList.remove('active');
}
});
}
/* =====================================
5. تهيئة وتحديث خريطة الحرارة - جديد
===================================== */
function initHeatMap() {
// تهيئة حاوية خريطة الحرارة
const heatBar = document.getElementById('heatBar');
if (!heatBar) return;
// إضافة معلومات توضيحية عند تحويم الماوس
heatBar.addEventListener('mouseover', function() {
const tooltip = document.createElement('div');
tooltip.className = 'absolute left-12 bg-black bg-opacity-80 text-white p-2 rounded text-xs';
tooltip.style.top = '50%';
tooltip.style.transform = 'translateY(-50%)';
tooltip.innerHTML = 'خريطة حرارة الأخطاء';
// حذف أي تلميحات سابقة
const oldTooltip = heatBar.querySelector('.absolute');
if (oldTooltip) {
oldTooltip.remove();
}
heatBar.appendChild(tooltip);
});
heatBar.addEventListener('mouseout', function() {
const tooltip = heatBar.querySelector('.absolute');
if (tooltip) {
tooltip.remove();
}
});
}
// تحديث خريطة الحرارة بعد التحليل أو تغيير الفلتر
function updateHeatMap() {
const heatBar = document.getElementById('heatBar');
if (!heatBar) return;
// مسح النقاط الحالية
while (heatBar.firstChild) {
heatBar.removeChild(heatBar.firstChild);
}
// الحصول على الطول الكلي للنص
let totalTextLength = 0;
analysisSegments.forEach(segment => {
totalTextLength += segment.source.length;
});
if (totalTextLength === 0) return;
// معالجة كل مقطع
let currentPosition = 0;
analysisSegments.forEach((segment, segmentIndex) => {
const segmentLength = segment.source.length;
const segmentStart = currentPosition / totalTextLength;
currentPosition += segmentLength;
// إضافة نقاط للأخطاء العددية
if (segment.errors.numbers > 0 && (currentErrorFilter === 'all' || currentErrorFilter === 'number')) {
for (let i = 0; i < segment.errors.numbers; i++) {
const position = (segmentStart + (i * 0.01)) * 100;
addHeatDot(position, 'number-error', `المقطع ${segmentIndex + 1} - خطأ رقمي`);
}
}
// إضافة نقاط للنصوص المفقودة
if (segment.errors.missing > 0 && (currentErrorFilter === 'all' || currentErrorFilter === 'missing')) {
for (let i = 0; i < segment.errors.missing; i++) {
const position = (segmentStart + (i * 0.01) + 0.003) * 100;
addHeatDot(position, 'missing-error', `المقطع ${segmentIndex + 1} - نص مفقود`);
}
}
// إضافة نقاط لأخطاء المعنى
if (segment.errors.meaning > 0 && (currentErrorFilter === 'all' || currentErrorFilter === 'meaning')) {
for (let i = 0; i < segment.errors.meaning; i++) {
const position = (segmentStart + (i * 0.01) + 0.006) * 100;
addHeatDot(position, 'meaning-error', `المقطع ${segmentIndex + 1} - خطأ معنى`);
}
}
});
}
// إضافة نقطة إلى خريطة الحرارة
function addHeatDot(positionPercent, errorClass, tooltipText) {
const heatBar = document.getElementById('heatBar');
if (!heatBar) return;
const dot = document.createElement('div');
dot.className = `heat-dot ${errorClass}`;
dot.style.top = `${positionPercent}%`;
dot.setAttribute('data-tooltip', tooltipText);
// إضافة حدث النقر للانتقال إلى المقطع المناسب
dot.addEventListener('click', function() {
// استخراج رقم المقطع من النص التلميحي
const segmentMatch = tooltipText.match(/المقطع (\d+)/);
if (segmentMatch && segmentMatch[1]) {
const segmentIndex = parseInt(segmentMatch[1]) - 1;
scrollToSegment(segmentIndex);
}
});
heatBar.appendChild(dot);
}
// التمرير إلى المقطع المحدد
function scrollToSegment(segmentIndex) {
const segmentElement = document.querySelector(`.segment-comparison[data-segment-index="${segmentIndex}"]`);
if (segmentElement) {
// التمرير إلى المقطع بتأثير ناعم
segmentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// إضافة تأثير بصري لتمييز المقطع
segmentElement.classList.add('animate-pulse');
setTimeout(() => {
segmentElement.classList.remove('animate-pulse');
}, 2000);
}
}
/* =====================================
إعداد طرق العرض وإزالة العرض الكلاسيكي
===================================== */
function setupViewModes() {
// إزالة العرض الكلاسيكي تمامًا
const classicViewBtn = document.querySelector('.view-btn[data-view="classicView"]');
const classicViewContainer = document.getElementById('classicView');
// إزالة زر العرض الكلاسيكي
if (classicViewBtn) {
classicViewBtn.remove();
}
// إزالة حاوية العرض الكلاسيكي
if (classicViewContainer) {
classicViewContainer.remove();
}
// تفعيل العرض المقسم كافتراضي
const segmentViewBtn = document.querySelector('.view-btn[data-view="segmentView"]');
if (segmentViewBtn) {
segmentViewBtn.classList.add('active');
}
const segmentViewContainer = document.getElementById('segmentView');
if (segmentViewContainer) {
segmentViewContainer.classList.add('active');
}
}
/* =====================================
تهيئة اختيار نوع الملف - محسن
===================================== */
function initFileTypeSelector() {
const fileTypeOptions = document.querySelectorAll('.file-type-option');
const descriptionElement = document.getElementById('fileTypeDescription');
const docTemplateContainer = document.getElementById('docTemplateContainer');
// إضافة مستمعي الأحداث لخيارات نوع الملف
fileTypeOptions.forEach(option => {
option.addEventListener('click', function() {
// إزالة التحديد من جميع الخيارات
fileTypeOptions.forEach(opt => opt.classList.remove('selected'));
// تحديد الخيار المختار
this.classList.add('selected');
// تحديث نوع الملف المختار
const radioInput = this.querySelector('input[type="radio"]');
radioInput.checked = true;
selectedFileType = radioInput.value;
// تحديث الوصف وإظهار/إخفاء قائمة القوالب
updateFileTypeDescription();
// إظهار/إخفاء قائمة القوالب الرسمية
if (selectedFileType === 'official') {
docTemplateContainer.classList.add('active');
updateTemplateInfo();
} else {
docTemplateContainer.classList.remove('active');
}
});
// إضافة حدث للراديو بوتن نفسه
const radioInput = option.querySelector('input[type="radio"]');
radioInput.addEventListener('change', function() {
if (this.checked) {
fileTypeOptions.forEach(opt => opt.classList.remove('selected'));
option.classList.add('selected');
selectedFileType = this.value;
updateFileTypeDescription();
// إظهار/إخفاء قائمة القوالب الرسمية
if (selectedFileType === 'official') {
docTemplateContainer.classList.add('active');
updateTemplateInfo();
} else {
docTemplateContainer.classList.remove('active');
}
}
});
});
// تحديث الوصف الأولي
updateFileTypeDescription();
}
/* =====================================
تحديث وصف نوع الملف - محسن
===================================== */
function updateFileTypeDescription() {
const descriptionElement = document.getElementById('fileTypeDescription');
if (selectedFileType === 'normal') {
descriptionElement.innerHTML = `
<i class="fas fa-info-circle ml-1"></i>
الملف العادي سيتم تحليله مباشرة بدون تصحيح مسبق
`;
} else if (selectedFileType === 'official') {
descriptionElement.innerHTML = `
<i class="fas fa-magic ml-1"></i>
المستند الرسمي سيتم تصحيح الثوابت فيه قبل التحليل (مثل تصحيح "الأثم" إلى "الاسم")
`;
}
}
/* =====================================
تهيئة النافذة المنبثقة المحسنة
===================================== */
function initEnhancedPopup() {
const popup = document.getElementById('enhancedErrorPopup');
const overlay = document.getElementById('popup-overlay');
const closeBtn = document.querySelector('.enhanced-popup-close');
const confirmBtn = document.getElementById('closeEnhancedPopup');
// إضافة حدث لزر الإغلاق
closeBtn.addEventListener('click', closeEnhancedPopup);
confirmBtn.addEventListener('click', closeEnhancedPopup);
// إضافة حدث للنقر على الخلفية
overlay.addEventListener('click', closeEnhancedPopup);
// إضافة مستمع للنقر على مفتاح Escape
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeEnhancedPopup();
}
});
}
/* =====================================
إغلاق النافذة المنبثقة المحسنة
===================================== */
function closeEnhancedPopup() {
const popup = document.getElementById('enhancedErrorPopup');
const overlay = document.getElementById('popup-overlay');
popup.classList.remove('show');
overlay.classList.remove('show');
}
/* =====================================
عرض النافذة المنبثقة المحسنة
===================================== */
function showEnhancedPopup(errorType, errorText, explanation, examples = null) {
const popup = document.getElementById('enhancedErrorPopup');
const overlay = document.getElementById('popup-overlay');
const title = document.getElementById('enhancedPopupTitle');
const content = document.getElementById('enhancedPopupContent');
// تعيين العنوان حسب نوع الخطأ
let typeIcon = '';
let typeClass = '';
let typeTitle = '';
if (errorType === 'number') {
typeTitle = 'خطأ في الأرقام';
typeIcon = '<i class="fas fa-hashtag text-yellow-500"></i>';
typeClass = 'border-yellow-400';
} else if (errorType === 'missing') {
typeTitle = 'نص مفقود';
typeIcon = '<i class="fas fa-minus-circle text-blue-500"></i>';
typeClass = 'border-blue-400';
} else if (errorType === 'meaning') {
typeTitle = 'اختلاف في المعنى';
typeIcon = '<i class="fas fa-exclamation-circle text-red-500"></i>';
typeClass = 'border-red-400';
} else {
typeTitle = 'تفاصيل الخطأ';
typeIcon = '<i class="fas fa-info-circle text-blue-500"></i>';
typeClass = 'border-blue-400';
}
title.innerHTML = `${typeIcon} ${typeTitle}`;
// إعداد المحتوى المحسن
let contentHTML = `
<div class="mb-4">
<h3 class="text-xl font-bold mb-3 text-gray-800">النص المحدد:</h3>
<div class="p-4 rounded-lg bg-gray-50 border-r-4 ${typeClass} text-lg">${errorText}</div>
</div>
<div class="mb-4">
<h3 class="text-xl font-bold mb-3 text-gray-800">تفسير الخطأ:</h3>
<div class="p-4 rounded-lg bg-blue-50 border border-blue-200 text-lg leading-relaxed">${explanation}</div>
</div>
`;
// إضافة أمثلة إذا كانت متوفرة
if (examples) {
contentHTML += `
<div class="mb-4">
<h3 class="text-xl font-bold mb-3 text-gray-800">أمثلة توضيحية:</h3>
<div class="space-y-4">
<div>
<div class="font-bold flex items-center text-red-700 mb-2">
<i class="fas fa-times-circle ml-2"></i> مثال خاطئ:
</div>
<div class="error-example">${examples.incorrect}</div>
</div>
<div>
<div class="font-bold flex items-center text-green-700 mb-2">
<i class="fas fa-check-circle ml-2"></i> مثال صحيح:
</div>
<div class="correct-example">${examples.correct}</div>
</div>
</div>
</div>
`;
}
// إضافة توضيح مرئي (صورة) حسب نوع الخطأ
let illustrationUrl = '';
if (errorType === 'number') {
illustrationUrl = 'https://i.ibb.co/Rvfwhdk/number-error.png';
} else if (errorType === 'missing') {
illustrationUrl = 'https://i.ibb.co/wYRgxWV/missing-text.png';
} else if (errorType === 'meaning') {
illustrationUrl = 'https://i.ibb.co/MPDc1X4/meaning-error.png';
}
if (illustrationUrl) {
contentHTML += `
<div class="mb-4">
<h3 class="text-xl font-bold mb-3 text-gray-800">توضيح مرئي:</h3>
<img src="${illustrationUrl}" alt="توضيح للخطأ" class="error-illustration">
</div>
`;
}
// إضافة نصائح للتصحيح
let tips = '';
if (errorType === 'number') {
tips = `
<li>تأكد من تطابق الأرقام بين النص المصدر والنص الهدف.</li>
<li>انتبه للأرقام بمختلف أنظمة كتابتها (عربي، هندي، إنجليزي).</li>
<li>تحقق من تنسيق الأرقام مثل استخدام الفواصل العشرية.</li>
`;
} else if (errorType === 'missing') {
tips = `
<li>أضف النص المفقود إلى الترجمة.</li>
<li>تأكد من ترجمة جميع عناصر النص المصدر بشكل كامل.</li>
<li>ابحث عن أي فقرات أو جمل منسية في النص الهدف.</li>
`;
} else if (errorType === 'meaning') {
tips = `
<li>راجع معنى النص في المصدر والهدف للتأكد من التطابق.</li>
<li>استخدم مصطلحات دقيقة ومناسبة للسياق.</li>
<li>تجنب الترجمة الحرفية التي قد تؤدي إلى فقدان المعنى الأصلي.</li>
`;
}
if (tips) {
contentHTML += `
<div class="p-4 rounded-lg bg-green-50 border border-green-200">
<h3 class="text-xl font-bold mb-3 text-gray-800 flex items-center">
<i class="fas fa-lightbulb text-yellow-500 ml-2"></i> نصائح للتصحيح:
</h3>
<ul class="list-disc pr-6 space-y-2 text-gray-700">
${tips}
</ul>
</div>
`;
}
content.innerHTML = contentHTML;
// عرض النافذة المنبثقة
overlay.classList.add('show');
popup.classList.add('show');
}
/* =====================================
إظهار/إخفاء مؤشر التحميل غير المتزامن
===================================== */
function showLoadingIndicator(text = "جاري معالجة البيانات...", progress = "0%") {
const indicator = document.getElementById('asyncLoadingIndicator');
const textElement = document.getElementById('asyncLoadingText');
const progressElement = document.getElementById('asyncLoadingProgress');
textElement.textContent = text;
progressElement.textContent = progress;
indicator.classList.add('active');
}
function hideLoadingIndicator() {
const indicator = document.getElementById('asyncLoadingIndicator');
indicator.classList.remove('active');
}
function updateLoadingProgress(text, progress) {
const textElement = document.getElementById('asyncLoadingText');
const progressElement = document.getElementById('asyncLoadingProgress');
textElement.textContent = text;
progressElement.textContent = progress;
}
/* =====================================
تصحيح النصوص للمستندات الرسمية - محسن لدعم القوالب
===================================== */
async function correctOfficialDocument(text, targetType) {
try {
// الحصول على نوع القالب المحدد
const templateSelect = document.getElementById('docTemplate');
const templateType = templateSelect ? templateSelect.value : 'other';
// عرض حالة التصحيح
document.getElementById('correctionStatus').classList.add('active');
document.getElementById('correctionMessage').textContent = 'جاري تحليل النص وتحديد الدولة...';
document.getElementById('correctionProgressBar').style.width = '10%';
// عرض مؤشر التحميل غير المتزامن
showLoadingIndicator('جاري تحليل المستند الرسمي وتصحيح الثوابت...', '10%');
// حفظ النص الأصلي
originalTextBeforeCorrection = text;
// تحديد الدولة أولاً
detectedCountry = await detectCountryFromText(text);
document.getElementById('correctionMessage').textContent = `تم تحديد الدولة: ${detectedCountry}. جاري تطبيق قواعد التصحيح...`;
document.getElementById('correctionProgressBar').style.width = '40%';
updateLoadingProgress(`تم تحديد الدولة: ${detectedCountry}. جاري تطبيق قواعد التصحيح...`, '40%');
// تطبيق التصحيحات بناءً على الدولة ونوع القالب
const correctionResult = await applyCorrectionRulesWithTemplate(text, detectedCountry, templateType);
document.getElementById('correctionProgressBar').style.width = '80%';
updateLoadingProgress('جاري تطبيق التصحيحات النهائية...', '80%');
// حفظ النص المُصحح والتغييرات
correctedText = correctionResult.correctedText;
correctionChanges = correctionResult.changes;
document.getElementById('correctionMessage').textContent = `تم تطبيق ${correctionChanges.length} تصحيح بنجاح`;
document.getElementById('correctionProgressBar').style.width = '100%';
updateLoadingProgress(`تم تطبيق ${correctionChanges.length} تصحيح بنجاح`, '100%');
// إخفاء حالة التصحيح وعرض المعاينة
setTimeout(() => {
document.getElementById('correctionStatus').classList.remove('active');
hideLoadingIndicator();
showCorrectionPreview();
}, 1000);
correctionApplied = true;
return correctedText;
} catch (error) {
console.error('خطأ في تصحيح المستند الرسمي:', error);
document.getElementById('correctionStatus').classList.remove('active');
hideLoadingIndicator();
addError('حدث خطأ أثناء تصحيح المستند الرسمي: ' + error.message, 'error');
return text; // إرجاع النص الأصلي في حالة الخطأ
}
}
/* =====================================
1. تطبيق قواعد التصحيح حسب القالب - محسن
===================================== */
async function applyCorrectionRulesWithTemplate(text, country, templateType) {
try {
// تحديد القواعد الخاصة بنوع المستند المحدد
let templateRules = '';
switch (templateType) {
case 'birth':
templateRules = `
- "تارخ الميلاد" → "تاريخ الميلاد"
- "تارخ / مكان الميلاد" → "تاريخ / مكان الميلاد"
- "مكان المولد" → "مكان الميلاد"
- "الموليد" → "الميلاد"
- "الأولاذ" → "الأولاد"
- "رقم القد" → "رقم القيد"
- "رقم واقعة الميلاد" → "رقم واقعة الميلاد"
- "جحة الميلاد" → "جهة الميلاد"
- "شهاده ميلاد" → "شهادة ميلاد"
- "تارخ التحرير" → "تاريخ التحرير"
`;
break;
case 'id':
templateRules = `
- "البطافة الشخصية" → "البطاقة الشخصية"
- "الرقو القومي" → "الرقم القومي"
- "الرقو الوطني" → "الرقم الوطني"
- "الرقو المدنى" → "الرقم المدني"
- "الرقو الموحد" → "الرقم الموحد"
- "الأثم" → "الاسم"
- "الأثم الكامل" → "الاسم الكامل"
- "تارخ الميلاد" → "تاريخ الميلاد"
- "محل الميلاذ" → "محل الميلاد"
- "الحنسية" → "الجنسية"
- "محل الإفامة" → "محل الإقامة"
- "العمل/المهنه" → "العمل/المهنة"
- "تارخ الأنتهاء" → "تاريخ الانتهاء"
- "تارخ الإصدار" → "تاريخ الإصدار"
- "البيناات" → "البيانات"
`;
break;
case 'passport':
templateRules = `
- "جواز سفر" → "جواز سفر"
- "جوار سفر" → "جواز سفر"
- "الأثم" → "الاسم"
- "الشهره" → "الشهرة"
- "النوع/الحنس" → "النوع/الجنس"
- "تارخ الإصدار" → "تاريخ الإصدار"
- "صالح لغايت" → "صالح لغاية"
- "تارخ الانتهاء" → "تاريخ الانتهاء"
- "صلاحيه حتى" → "صلاحية حتى"
- "رقو الجواز" → "رقم الجواز"
- "السلطه المصدرة" → "السلطة المصدرة"
`;
break;
case 'driving':
templateRules = `
- "رخصة فياده" → "رخصة قيادة"
- "رخصة قياده" → "رخصة قيادة"
- "تارخ الإصدار" → "تاريخ الإصدار"
- "الرقو" → "الرقم"
- "اسم المالك/صاخب الترخيص" → "اسم المالك/صاحب الترخيص"
- "ساريه المفعول حتى" → "سارية المفعول حتى"
- "صالحيه حتى" → "صالحية حتى"
- "الغئة" → "الفئة"
- "أقر بإستخدام النقارة" → "أقر باستخدام النظارة"
`;
break;
default: // other
templateRules = `
- "الأثم" → "الاسم"
- "تارخ" → "تاريخ"
- "الموليد" → "المولد"
- "الحنسية" → "الجنسية"
- "الرقو" → "الرقم"
- "الهويه" → "الهوية"
- "البيناات" → "البيانات"
- "الشحصية" → "الشخصية"
- "اسم الشخس" → "اسم الشخص"
`;
break;
}
const prompt = `صحح الأخطاء في الثوابت فقط (وليس المتغيرات) في النص التالي من مستند رسمي ${country}ي من نوع "${getTemplateNameInArabic(templateType)}":
قواعد التصحيح:
1. صحح أخطاء الكلمات الثابتة مثل:
${templateRules}
2. صحح الأخطاء الإملائية في المصطلحات الرسمية حسب معايير ${country}
3. لا تُغيّر أي بيانات شخصية (الأسماء، التواريخ، الأرقام، العناوين)
4. احتفظ بالتنسيق الأصلي للنص تماماً
أعِد النص المُصحح مع قائمة بالتغييرات في هذا الشكل:
النص المُصحح:
[النص هنا]
التغييرات المطبقة:
- [الكلمة الخطأ] → [الكلمة الصحيحة]
النص الأصلي:
${text}`;
const payload = {
model: "deepseek-chat",
messages: [
{ role: "system", content: "أنت خبير في تصحيح المستندات الرسمية العربية وتطبيق قواعد الإملاء والنحو المناسبة لكل دولة ونوع مستند" },
{ role: "user", content: prompt }
],
temperature: 0.2,
max_tokens: 4000
};
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('فشل في تطبيق قواعد التصحيح');
}
const data = await response.json();
const result = data.choices[0].message.content.trim();
// استخراج النص المُصحح والتغييرات
const correctedTextMatch = result.match(/النص المُصحح:\s*([\s\S]*?)(?=التغييرات المطبقة:|$)/);
const changesMatch = result.match(/التغييرات المطبقة:\s*([\s\S]*)/);
let correctedText = text; // افتراضي: النص الأصلي
let changes = [];
if (correctedTextMatch) {
correctedText = correctedTextMatch[1].trim();
}
if (changesMatch) {
const changesText = changesMatch[1].trim();
// استخراج التغييرات من النص
const changeLines = changesText.split('\n').filter(line => line.includes('→'));
changes = changeLines.map(line => {
const parts = line.replace(/^-\s*/, '').split('→');
if (parts.length === 2) {
return {
original: parts[0].trim(),
corrected: parts[1].trim()
};
}
return null;
}).filter(change => change !== null);
}
return {
correctedText: correctedText,
changes: changes
};
} catch (error) {
console.error('خطأ في تطبيق قواعد التصحيح:', error);
throw error;
}
}
// 1. الحصول على اسم نوع القالب بالعربية
function getTemplateNameInArabic(templateType) {
switch (templateType) {
case 'birth': return 'شهادة ميلاد';
case 'id': return 'بطاقة شخصية';
case 'passport': return 'جواز سفر';
case 'driving': return 'رخصة قيادة';
default: return 'مستند رسمي';
}
}
/* =====================================
تحديد الدولة من النص - محسن
===================================== */
async function detectCountryFromText(text) {
try {
const prompt = `حلل النص التالي وحدد الدولة التي ينتمي إليها هذا المستند الرسمي بناءً على:
1. المصطلحات المستخدمة
2. أسلوب الكتابة الرسمية
3. الكلمات المميزة للدولة
أجب بكلمة واحدة فقط اسم الدولة بالعربية.
النص:
${text.substring(0, 500)}`;
const payload = {
model: "deepseek-chat",
messages: [
{ role: "system", content: "أنت خبير في تحديد مصدر المستندات الرسمية العربية" },
{ role: "user", content: prompt }
],
temperature: 0.1,
max_tokens: 50
};
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('فشل في تحديد الدولة');
}
const data = await response.json();
const country = data.choices[0].message.content.trim();
// تنظيف النتيجة للحصول على اسم الدولة فقط
const cleanCountry = country.replace(/[^\u0600-\u06FF\s]/g, '').trim();
return cleanCountry || 'مصر'; // افتراضي: مصر
} catch (error) {
console.error('خطأ في تحديد الدولة:', error);
return 'مصر'; // قيمة افتراضية
}
}
/* =====================================
عرض معاينة التصحيحات - محسنة
===================================== */
function showCorrectionPreview() {
const previewElement = document.getElementById('correctionPreview');
const changesElement = document.getElementById('correctionChanges');
if (correctionChanges.length === 0) {
changesElement.innerHTML = '<p class="text-gray-500">لم يتم العثور على أخطاء تحتاج إلى تصحيح في الثوابت</p>';
} else {
let changesHTML = '';
correctionChanges.forEach(change => {
changesHTML += `
<div class="mb-2">
<span class="original-text">${change.original}</span>
<span class="mx-2">→</span>
<span class="corrected-text">${change.corrected}</span>
</div>
`;
});
changesElement.innerHTML = changesHTML;
}
previewElement.classList.add('active');
}
/* =====================================
دالة المعالجة الرئيسية للملفات - محسنة
===================================== */
function processFile(file, targetType) {
if (!file) return;
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري فحص نوع الملف...';
document.getElementById('progressBar').style.width = '10%';
// عرض مؤشر التحميل غير المتزامن
showLoadingIndicator('جاري فحص وتجهيز الملف...', '10%');
currentProcessingMode = targetType;
// تحديد نوع الملف وتوجيهه للمعالجة المناسبة
if (file.type === 'application/pdf') {
processPDF(file, targetType);
} else if (file.type.startsWith('image/')) {
processImage(file, targetType);
} else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
file.name.toLowerCase().endsWith('.docx')) {
processDocx(file, targetType);
} else if (file.type === 'application/vnd.ms-excel' ||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
file.name.toLowerCase().endsWith('.xlsx') ||
file.name.toLowerCase().endsWith('.xls')) {
processExcel(file, targetType);
} else {
// إذا كان نوع الملف غير مدعوم
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
alert('نوع الملف غير مدعوم. يرجى تحميل ملف PDF أو صورة أو Word أو Excel.');
}
}
/* =====================================
معالجة ملف PDF - محسنة
===================================== */
async function processPDF(file, targetType) {
try {
document.getElementById('statusText').textContent = 'جاري معالجة ملف PDF...';
document.getElementById('progressBar').style.width = '20%';
updateLoadingProgress('جاري معالجة ملف PDF...', '20%');
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument(arrayBuffer);
const pdf = await loadingTask.promise;
documentPages = [];
selectedPages = [];
document.getElementById('progressBar').style.width = '50%';
updateLoadingProgress('جاري استخراج صفحات PDF...', '50%');
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
updateLoadingProgress(`جاري استخراج الصفحة ${i} من ${pdf.numPages}...`, `${Math.round(50 + (i/pdf.numPages) * 20)}%`);
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
documentPages.push({
pageNum: i,
imageData: canvas.toDataURL('image/jpeg'),
selected: true // تحديد كل الصفحات تلقائيًا
});
selectedPages.push(i-1); // إضافة الصفحة للصفحات المحددة
}
document.getElementById('progressBar').style.width = '70%';
updateLoadingProgress('جاري تحضير الصفحات للعرض...', '70%');
document.getElementById('pdfPagesCard').classList.remove('hidden');
displayPDFPages();
// بدء معالجة OCR تلقائيًا
document.getElementById('progressBar').style.width = '80%';
updateLoadingProgress('جاري استخراج النص من الصفحات...', '80%');
await extractText();
} catch (error) {
console.error('خطأ في معالجة ملف PDF:', error);
alert('حدث خطأ أثناء معالجة ملف PDF');
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}
}
/* =====================================
معالجة ملف صورة - محسنة
===================================== */
function processImage(file, targetType) {
document.getElementById('statusText').textContent = 'جاري معالجة الصورة...';
document.getElementById('progressBar').style.width = '30%';
updateLoadingProgress('جاري معالجة الصورة...', '30%');
const reader = new FileReader();
reader.onload = async function(e) {
const img = new Image();
img.onload = async function() {
documentPages = [{
pageNum: 1,
imageData: e.target.result,
selected: true
}];
selectedPages = [0];
document.getElementById('progressBar').style.width = '60%';
updateLoadingProgress('جاري تحضير الصورة للعرض والمعالجة...', '60%');
document.getElementById('pdfPagesCard').classList.remove('hidden');
displayPDFPages();
// بدء معالجة OCR تلقائيًا
document.getElementById('progressBar').style.width = '80%';
updateLoadingProgress('جاري استخراج النص من الصورة...', '80%');
await extractText();
};
img.src = e.target.result;
};
reader.onerror = function() {
console.error('خطأ في قراءة الصورة');
alert('حدث خطأ أثناء قراءة الصورة');
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
};
reader.readAsDataURL(file);
}
/* =====================================
معالجة ملف Word (DOCX) - محسنة
===================================== */
async function processDocx(file, targetType) {
try {
document.getElementById('statusText').textContent = 'جاري معالجة ملف Word...';
document.getElementById('progressBar').style.width = '30%';
updateLoadingProgress('جاري معالجة ملف Word...', '30%');
const arrayBuffer = await file.arrayBuffer();
// تحسين استخراج النص من DOCX مع محاولة معالجة المستندات الرسمية
updateLoadingProgress('جاري استخراج النص من ملف Word...', '50%');
try {
// استخدام مكتبة Mammoth لاستخراج النص من DOCX
const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer });
document.getElementById('progressBar').style.width = '70%';
if (result && result.value) {
let extractedText = result.value;
updateLoadingProgress('تم استخراج النص بنجاح!', '80%');
// تطبيق التصحيح إذا كان الملف مستنداً رسمياً
if (selectedFileType === 'official') {
updateLoadingProgress('جاري تصحيح المستند الرسمي...', '85%');
extractedText = await correctOfficialDocument(extractedText, targetType);
}
// تحديث شريط التقدم
document.getElementById('progressBar').style.width = '90%';
updateLoadingProgress('جاري تحضير النتائج...', '90%');
// عرض النص المستخرج
displayDocxExtractedText(extractedText);
// استخدام النص كمصدر أو هدف
if (targetType === 'source') {
document.getElementById('sourceText').value = extractedText;
addError('تم استخراج النص من ملف Word وإضافته كنص مصدر', 'info');
} else if (targetType === 'target') {
document.getElementById('targetText').value = extractedText;
addError('تم استخراج النص من ملف Word وإضافته كنص هدف', 'info');
} else if (targetType === 'extra') {
document.getElementById('sourceExtraText').value = extractedText;
addError('تم استخراج النص من ملف Word وإضافته كمصدر إضافي', 'info');
}
document.getElementById('progressBar').style.width = '100%';
updateLoadingProgress('تمت العملية بنجاح!', '100%');
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}, 1000);
} else {
throw new Error('فشل في استخراج النص من الملف');
}
} catch (mammothError) {
console.error('خطأ في استخراج النص باستخدام Mammoth:', mammothError);
// محاولة استخدام طريقة بديلة لاستخراج النص
updateLoadingProgress('جاري محاولة استخراج النص بطريقة بديلة...', '60%');
try {
// استخدام OCR كآلية احتياطية لاستخراج النص
// تحويل الـ DOCX إلى صورة أولاً
const docxContent = new Uint8Array(arrayBuffer);
const blob = new Blob([docxContent], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
// استخراج نص بديل
const fallbackText = await extractFallbackTextFromDocx(blob);
if (fallbackText) {
let extractedText = fallbackText;
// تطبيق التصحيح إذا كان الملف مستنداً رسمياً
if (selectedFileType === 'official') {
updateLoadingProgress('جاري تصحيح المستند الرسمي...', '85%');
extractedText = await correctOfficialDocument(extractedText, targetType);
}
// عرض النص المستخرج
displayDocxExtractedText(extractedText);
// استخدام النص كمصدر أو هدف
if (targetType === 'source') {
document.getElementById('sourceText').value = extractedText;
addError('تم استخراج النص من ملف Word وإضافته كنص مصدر (باستخدام طريقة بديلة)', 'info');
} else if (targetType === 'target') {
document.getElementById('targetText').value = extractedText;
addError('تم استخراج النص من ملف Word وإضافته كنص هدف (باستخدام طريقة بديلة)', 'info');
} else if (targetType === 'extra') {
document.getElementById('sourceExtraText').value = extractedText;
addError('تم استخراج النص من ملف Word وإضافته كمصدر إضافي (باستخدام طريقة بديلة)', 'info');
}
document.getElementById('progressBar').style.width = '100%';
updateLoadingProgress('تمت العملية بنجاح!', '100%');
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}, 1000);
} else {
throw new Error('فشل في استخراج النص بالطريقة البديلة');
}
} catch (fallbackError) {
console.error('خطأ في استخراج النص بالطريقة البديلة:', fallbackError);
throw new Error('فشل في استخراج النص من الملف بكلتا الطريقتين');
}
}
} catch (error) {
console.error('خطأ في معالجة ملف Word:', error);
alert('حدث خطأ أثناء معالجة ملف Word: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}
}
/* =====================================
استخراج نص بديل من ملف Word
===================================== */
async function extractFallbackTextFromDocx(blob) {
try {
// يتم استخدام هذه الدالة كبديل عندما تفشل مكتبة Mammoth
// نستخدم هنا خوارزمية مبسطة لاستخراج النص من ملف DOCX
// تحويل البلوب إلى أراي بفر
const arrayBuffer = await blob.arrayBuffer();
const data = new Uint8Array(arrayBuffer);
// بحث عن سلاسل النصوص في ملف DOCX (مضغوط)
const textParts = [];
let currentText = '';
// البحث عن النصوص العربية والإنجليزية
const arabicOrEnglishRegex = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\uFB50-\uFDFF\uFE70-\uFEFF\s\da-zA-Z.,;:'"!@#$%^&*()_+-=[\]{}|<>/?\\~`]+/g;
// تحويل الباينري إلى نص
const text = String.fromCharCode.apply(null, data);
// استخراج الأجزاء النصية
const matches = text.match(arabicOrEnglishRegex);
if (matches && matches.length > 0) {
// تجميع النصوص المستخرجة
return matches.join(' ').replace(/\s+/g, ' ').trim();
}
return null;
} catch (error) {
console.error('خطأ في استخراج النص البديل:', error);
return null;
}
}
/* =====================================
عرض النص المستخرج من ملف Word - محسنة
===================================== */
function displayDocxExtractedText(text) {
// هنا يمكن إضافة معالجة إضافية للنص المستخرج إذا لزم الأمر
document.getElementById('resultsCard').classList.remove('hidden');
document.getElementById('pdfPagesCard').classList.add('hidden');
document.getElementById('resultPreview').innerHTML = `
<div class="page-preview">
<h4>النص المستخرج من ملف Word${correctionApplied ? ' (مُصحح)' : ''}</h4>
<div>${text.substring(0, 200)}${text.length > 200 ? '...' : ''}</div>
</div>
`;
document.getElementById('resultText').textContent = text;
}
/* =====================================
معالجة ملف Excel - محسنة
===================================== */
function processExcel(file, targetType) {
document.getElementById('statusText').textContent = 'جاري معالجة ملف Excel...';
document.getElementById('progressBar').style.width = '30%';
updateLoadingProgress('جاري معالجة ملف Excel...', '30%');
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
excelWorkbook = XLSX.read(data, {type: 'array'});
// تحديث شريط التقدم
document.getElementById('progressBar').style.width = '70%';
updateLoadingProgress('جاري استخراج البيانات من ملف Excel...', '70%');
if (excelWorkbook.SheetNames.length > 0) {
// عرض قائمة بأسماء الأوراق
renderSheetSelector(excelWorkbook.SheetNames, targetType);
// اختيار الورقة الأولى تلقائيًا
selectExcelSheet(excelWorkbook.SheetNames[0], targetType);
updateLoadingProgress('تم استخراج البيانات بنجاح!', '90%');
// استخدام النص كمصدر أو هدف تلقائيًا
useExcelContent(targetType);
} else {
alert('لم يتم العثور على أوراق في ملف Excel');
hideLoadingIndicator();
}
document.getElementById('progressBar').style.width = '100%';
updateLoadingProgress('تمت العملية بنجاح!', '100%');
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}, 500);
} catch (error) {
console.error('خطأ في معالجة ملف Excel:', error);
alert('حدث خطأ أثناء معالجة ملف Excel');
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}
};
reader.onerror = function() {
alert('حدث خطأ أثناء قراءة الملف');
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
};
reader.readAsArrayBuffer(file);
}
/* =====================================
عرض صفحات PDF - محسنة
===================================== */
function displayPDFPages() {
const container = document.getElementById('pdfPagesContainer');
container.innerHTML = '';
documentPages.forEach((page, index) => {
const pageDiv = document.createElement('div');
pageDiv.className = `pdf-page ${page.selected ? 'selected' : ''}`;
pageDiv.dataset.index = index;
const img = document.createElement('img');
img.src = page.imageData;
img.alt = `Page ${page.pageNum}`;
const pageNumDiv = document.createElement('div');
pageNumDiv.className = 'page-number';
pageNumDiv.textContent = page.pageNum;
pageDiv.appendChild(img);
pageDiv.appendChild(pageNumDiv);
container.appendChild(pageDiv);
pageDiv.addEventListener('click', function() {
this.classList.toggle('selected');
documentPages[index].selected = !documentPages[index].selected;
if (documentPages[index].selected) {
if (!selectedPages.includes(index)) {
selectedPages.push(index);
}
} else {
const pos = selectedPages.indexOf(index);
if (pos !== -1) {
selectedPages.splice(pos, 1);
}
}
});
});
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}
/* =====================================
تحديد/إلغاء تحديد كل الصفحات
===================================== */
function selectAllPages() {
selectedPages = [];
documentPages.forEach((page, index) => {
page.selected = true;
selectedPages.push(index);
});
document.querySelectorAll('.pdf-page').forEach(pageDiv => {
pageDiv.classList.add('selected');
});
}
function deselectAllPages() {
selectedPages = [];
documentPages.forEach(page => {
page.selected = false;
});
document.querySelectorAll('.pdf-page').forEach(pageDiv => {
pageDiv.classList.remove('selected');
});
}
/* =====================================
استخراج النص من الصفحات المحددة - محسنة بشكل كبير
===================================== */
async function extractText() {
if (selectedPages.length === 0) {
alert('الرجاء اختيار صفحة واحدة على الأقل');
return;
}
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري استخراج النص...';
document.getElementById('progressBar').style.width = '0%';
showLoadingIndicator('جاري تحضير الصفحات لاستخراج النص...', '0%');
extractedTexts = [];
extractedPageNumbers = [];
try {
for (let i = 0; i < selectedPages.length; i++) {
const pageIndex = selectedPages[i];
const pageData = documentPages[pageIndex];
document.getElementById('statusText').textContent = `جاري استخراج النص من الصفحة ${pageData.pageNum}...`;
document.getElementById('progressBar').style.width = `${(i / selectedPages.length) * 70}%`;
updateLoadingProgress(`جاري استخراج النص من الصفحة ${pageData.pageNum} (${i+1} من ${selectedPages.length})...`,
`${Math.round((i / selectedPages.length) * 70)}%`);
// استخدام API الـ OCR بشكل غير متزامن
const extractedText = await extractTextFromImage(pageData.imageData, pageData.pageNum);
extractedTexts.push(extractedText);
extractedPageNumbers.push(pageData.pageNum);
// تحديث العداد وتاريخ آخر معالجة في localStorage
ocrPagesCount++;
localStorage.setItem('ocrPagesCount', ocrPagesCount);
document.getElementById('ocrCounter').textContent = ocrPagesCount;
const now = new Date();
const dateStr = now.toLocaleDateString('ar-EG');
localStorage.setItem('lastOcrDate', dateStr);
document.getElementById('lastOcrDate').textContent = dateStr;
}
document.getElementById('progressBar').style.width = '80%';
updateLoadingProgress('جاري معالجة النصوص المستخرجة...', '80%');
// دمج النصوص المستخرجة
let combinedText = extractedTexts.join('\n\n');
// تطبيق التصحيح إذا كان الملف مستنداً رسمياً
if (selectedFileType === 'official') {
updateLoadingProgress('جاري تصحيح النص المستخرج للمستند الرسمي...', '85%');
combinedText = await correctOfficialDocument(combinedText, currentProcessingMode);
}
updateLoadingProgress('جاري تحضير النتائج...', '90%');
// عرض النصوص المستخرجة
displayExtractedTexts();
// استخدام النص تلقائيًا
if (currentProcessingMode === 'source') {
document.getElementById('sourceText').value = combinedText;
addError('تم إضافة النص المستخرج كنص مصدر بنجاح', 'info');
} else if (currentProcessingMode === 'target') {
document.getElementById('targetText').value = combinedText;
addError('تم إضافة النص المستخرج كنص هدف بنجاح', 'info');
} else if (currentProcessingMode === 'extra') {
document.getElementById('sourceExtraText').value = combinedText;
addError('تم إضافة النص المستخرج كمصدر إضافي بنجاح', 'info');
}
updateLoadingProgress('تمت العملية بنجاح!', '100%');
document.getElementById('progressBar').style.width = '100%';
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}, 1000);
} catch (error) {
console.error('خطأ في استخراج النص:', error);
alert('حدث خطأ أثناء استخراج النص: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
hideLoadingIndicator();
}
}
/* =====================================
استخراج النص من صورة - محسنة
===================================== */
async function extractTextFromImage(imageData, pageNumber) {
try {
// تحويل Data URL إلى Blob
const response = await fetch(imageData);
const blob = await response.blob();
// إنشاء FormData وإضافة الصورة
const formData = new FormData();
formData.append('image', blob);
formData.append('language', 'ara'); // تحديد اللغة العربية
// طلب OCR
const ocrResponse = await fetch(OCR_API_URL, {
method: 'POST',
headers: {
'x-rapidapi-key': RAPIDAPI_KEY,
'x-rapidapi-host': 'ocr43.p.rapidapi.com'
},
body: formData
});
if (!ocrResponse.ok) {
throw new Error(`فشل في طلب OCR: ${ocrResponse.status}`);
}
const data = await ocrResponse.json();
try {
// استخراج النص من الاستجابة مع الحفاظ على تنسيق الصفوف
if (data && data.results && data.results[0] && data.results[0].entities &&
data.results[0].entities[0] && data.results[0].entities[0].objects &&
data.results[0].entities[0].objects[0] && data.results[0].entities[0].objects[0].entities &&
data.results[0].entities[0].objects[0].entities[0] && data.results[0].entities[0].objects[0].entities[0].text) {
const text = data.results[0].entities[0].objects[0].entities[0].text;
return text;
} else {
// محاولة استخراج النص بطريقة بديلة
if (data && data.results && data.results[0] && data.results[0].text) {
return data.results[0].text;
}
return `[لم يتم العثور على نص في الصفحة ${pageNumber}]`;
}
} catch (e) {
console.error('Error parsing OCR response:', e);
return `[خطأ في معالجة النص للصفحة ${pageNumber}]`;
}
} catch (error) {
console.error(`Error in OCR for page ${pageNumber}:`, error);
throw error;
}
}
/* =====================================
عرض النصوص المستخرجة - محسنة
===================================== */
function displayExtractedTexts() {
document.getElementById('pdfPagesCard').classList.add('hidden');
document.getElementById('resultsCard').classList.remove('hidden');
document.getElementById('processingStatus').classList.add('hidden');
const resultPreview = document.getElementById('resultPreview');
resultPreview.innerHTML = '';
extractedTexts.forEach((text, index) => {
const pagePreview = document.createElement('div');
pagePreview.className = 'page-preview';
pagePreview.innerHTML = `
<h4>الصفحة ${extractedPageNumbers[index]}${correctionApplied ? ' (مُصححة)' : ''}</h4>
<div>${text.substring(0, 100)}${text.length > 100 ? '...' : ''}</div>
`;
resultPreview.appendChild(pagePreview);
});
// عرض النص الكامل (المُصحح إذا لزم الأمر)
const finalText = correctionApplied ? correctedText : extractedTexts.join('\n\n');
document.getElementById('resultText').textContent = finalText;
}
/* =====================================
نسخ النص المستخرج
===================================== */
function copyText() {
const resultText = document.getElementById('resultText');
// إنشاء عنصر textarea مؤقت
const textarea = document.createElement('textarea');
textarea.value = resultText.textContent;
document.body.appendChild(textarea);
// اختيار النص ونسخه
textarea.select();
document.execCommand('copy');
// إزالة العنصر المؤقت
document.body.removeChild(textarea);
alert('تم نسخ النص');
}
/* =====================================
تنزيل النص المستخرج
===================================== */
function downloadText() {
const text = document.getElementById('resultText').textContent;
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'extracted_text.txt';
a.click();
URL.revokeObjectURL(url);
}
/* =====================================
استخدام النص المستخرج - محسنة
===================================== */
function useOcrText(target) {
const text = document.getElementById('resultText').textContent;
if (target === 'source') {
document.getElementById('sourceText').value = text;
} else {
document.getElementById('targetText').value = text;
}
const message = `تم استخدام النص المستخرج${correctionApplied ? ' المُصحح' : ''} كنص ${target === 'source' ? 'مصدر' : 'هدف'}`;
alert(message);
}
/* =====================================
وظائف محرر الصور
===================================== */
function initializeImageEditor(index) {
currentPageIndex = index;
const imageCanvas = document.getElementById('imageCanvas');
const imageEditor = document.getElementById('imageEditor');
imageEditor.classList.remove('hidden');
// حفظ البيانات الأصلية للصورة
originalImageData = documentPages[index].imageData;
// إنشاء كائن Fabric canvas
if (fabricCanvas) {
fabricCanvas.dispose();
}
fabricCanvas = new fabric.Canvas('imageCanvas');
// تحميل الصورة
fabric.Image.fromURL(originalImageData, function(img) {
// تحجيم الصورة لتناسب الكانفاس
const containerWidth = imageCanvas.parentElement.clientWidth;
const scale = containerWidth / img.width;
img.scale(scale);
fabricCanvas.setWidth(img.width * scale);
fabricCanvas.setHeight(img.height * scale);
fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas));
});
}
function rotateImageLeft() {
if (!fabricCanvas) return;
const img = fabricCanvas.backgroundImage;
img.rotate((img.angle || 0) - 90);
fabricCanvas.renderAll();
updateModifiedImage();
}
function rotateImageRight() {
if (!fabricCanvas) return;
const img = fabricCanvas.backgroundImage;
img.rotate((img.angle || 0) + 90);
fabricCanvas.renderAll();
updateModifiedImage();
}
function flipImageHorizontal() {
if (!fabricCanvas) return;
const img = fabricCanvas.backgroundImage;
img.set('flipX', !img.flipX);
fabricCanvas.renderAll();
updateModifiedImage();
}
function flipImageVertical() {
if (!fabricCanvas) return;
const img = fabricCanvas.backgroundImage;
img.set('flipY', !img.flipY);
fabricCanvas.renderAll();
updateModifiedImage();
}
function activateCropMode() {
if (!fabricCanvas) return;
if (isInCropMode) {
return;
}
isInCropMode = true;
document.getElementById('cropImage').textContent = 'تطبيق القص';
// إنشاء مربع للقص
cropRect = new fabric.Rect({
left: 50,
top: 50,
width: fabricCanvas.width - 100,
height: fabricCanvas.height - 100,
fill: 'rgba(0,0,0,0.2)',
stroke: 'black',
strokeDashArray: [5, 5],
strokeWidth: 2,
selectable: true
});
fabricCanvas.add(cropRect);
fabricCanvas.setActiveObject(cropRect);
}
function applyCrop() {
if (!fabricCanvas || !cropRect) return;
isInCropMode = false;
document.getElementById('cropImage').textContent = 'قص الصورة';
// الحصول على معلومات مستطيل القص
const rect = cropRect;
const img = fabricCanvas.backgroundImage;
// إزالة مستطيل القص
fabricCanvas.remove(rect);
// إنشاء كانفاس جديد للقص
const cropCanvas = document.createElement('canvas');
const cropContext = cropCanvas.getContext('2d');
cropCanvas.width = rect.getScaledWidth();
cropCanvas.height = rect.getScaledHeight();
// إعداد المصدر والوجهة للقص
const sourceLeft = rect.left;
const sourceTop = rect.top;
const sourceWidth = rect.getScaledWidth();
const sourceHeight = rect.getScaledHeight();
// إنشاء صورة جديدة من الصورة الأصلية
const tempImage = new Image();
tempImage.onload = function() {
// رسم الجزء المقصوص من الصورة
cropContext.drawImage(
tempImage,
sourceLeft / img.scaleX,
sourceTop / img.scaleY,
sourceWidth / img.scaleX,
sourceHeight / img.scaleY,
0, 0,
cropCanvas.width,
cropCanvas.height
);
// تحديث صورة الخلفية
fabric.Image.fromURL(cropCanvas.toDataURL(), function(newImg) {
fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas));
fabricCanvas.setDimensions({
width: cropCanvas.width,
height: cropCanvas.height
});
updateModifiedImage();
});
};
tempImage.src = img._element.src;
}
function resetImage() {
if (!fabricCanvas) return;
// إعادة الصورة إلى حالتها الأصلية
fabric.Image.fromURL(originalImageData, function(img) {
const containerWidth = document.getElementById('imageCanvas').parentElement.clientWidth;
const scale = containerWidth / img.width;
img.scale(scale);
fabricCanvas.setWidth(img.width * scale);
fabricCanvas.setHeight(img.height * scale);
fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas));
// إزالة أي عناصر إضافية
fabricCanvas.clear();
// إعادة تعيين حالة القص
isInCropMode = false;
cropRect = null;
document.getElementById('cropImage').textContent = 'قص الصورة';
// تحديث الصورة المعدلة
updateModifiedImage();
});
}
function improveContrast() {
if (!fabricCanvas) return;
const img = fabricCanvas.backgroundImage;
// إنشاء كانفاس مؤقت
const tempCanvas = document.createElement('canvas');
const tempContext = tempCanvas.getContext('2d');
tempCanvas.width = img.width * img.scaleX;
tempCanvas.height = img.height * img.scaleY;
// رسم الصورة على الكانفاس المؤقت
tempContext.drawImage(img._element, 0, 0, tempCanvas.width, tempCanvas.height);
// الحصول على بيانات الصورة
const imageData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
const data = imageData.data;
// تحسين التباين
const factor = 1.5; // عامل التباين
for (let i = 0; i < data.length; i += 4) {
// الحصول على القيم RGB
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// حساب القيمة الجديدة
data[i] = Math.min(255, Math.max(0, factor * (r - 128) + 128));
data[i + 1] = Math.min(255, Math.max(0, factor * (g - 128) + 128));
data[i + 2] = Math.min(255, Math.max(0, factor * (b - 128) + 128));
}
// وضع البيانات المعدلة في الكانفاس
tempContext.putImageData(imageData, 0, 0);
// تحديث صورة الخلفية
fabric.Image.fromURL(tempCanvas.toDataURL(), function(newImg) {
fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas));
updateModifiedImage();
});
}
function updateModifiedImage() {
// تحديث الصورة المعدلة في مصفوفة الصفحات
if (fabricCanvas && currentPageIndex !== -1) {
documentPages[currentPageIndex].imageData = fabricCanvas.toDataURL();
}
}
/* =====================================
رندر أزرار اختيار أوراق Excel
===================================== */
function renderSheetSelector(sheetNames, targetType) {
const container = document.getElementById('sheetSelectorContainer');
container.innerHTML = '';
sheetNames.forEach(sheetName => {
const btn = document.createElement('div');
btn.className = 'sheet-selector';
btn.textContent = sheetName;
btn.onclick = function() {
document.querySelectorAll('.sheet-selector').forEach(b => b.classList.remove('active'));
this.classList.add('active');
selectExcelSheet(sheetName, targetType);
};
container.appendChild(btn);
});
// تفعيل الورقة الأولى
const firstSheet = container.querySelector('.sheet-selector');
if (firstSheet) {
firstSheet.classList.add('active');
}
// إظهار قسم معاينة Excel
document.getElementById('excelPreviewCard').classList.remove('hidden');
}
/* =====================================
اختيار ورقة Excel
===================================== */
function selectExcelSheet(sheetName, targetType) {
currentSheetName = sheetName;
// تحويل الورقة إلى مصفوفة
const worksheet = excelWorkbook.Sheets[sheetName];
const data = XLSX.utils.sheet_to_json(worksheet, {header: 1});
// تخزين البيانات للاستخدام لاحقًا
excelData = {
targetType: targetType,
sheetName: sheetName,
data: data
};
// عرض البيانات في جدول
renderExcelTable(data);
}
/* =====================================
رندر جدول Excel
===================================== */
function renderExcelTable(data) {
const container = document.getElementById('excelContent');
if (!data || data.length === 0) {
container.innerHTML = '<p class="text-center p-4 text-gray-500">لا توجد بيانات في هذه الورقة</p>';
return;
}
let html = '<table class="preview-table"><thead><tr>';
// إنشاء الترويسة باستخدام الصف الأول أو محتوى عمود
const headerRow = data[0];
for (let i = 0; i < headerRow.length; i++) {
html += `<th>${headerRow[i] || 'عمود ' + (i+1)}</th>`;
}
html += '</tr></thead><tbody>';
// إضافة صفوف البيانات
for (let i = 1; i < data.length; i++) {
html += '<tr>';
const row = data[i];
for (let j = 0; j < headerRow.length; j++) {
html += `<td>${row[j] !== undefined ? row[j] : ''}</td>`;
}
html += '</tr>';
}
html += '</tbody></table>';
container.innerHTML = html;
}
/* =====================================
استخدام محتوى Excel كنص
===================================== */
function useExcelContent(target) {
if (!excelData || !excelData.data) {
alert('لم يتم تحميل بيانات Excel');
return;
}
// تحويل البيانات إلى نص
let textContent = '';
// تخطي صف الترويسة وبدء من الصف 1
for (let i = 1; i < excelData.data.length; i++) {
const row = excelData.data[i];
if (row && row.length > 0) {
textContent += row.join('\t') + '\n';
}
}
// تحديث النص المصدر أو الهدف
if (target === 'source') {
document.getElementById('sourceText').value = textContent;
} else if (target === 'target') {
document.getElementById('targetText').value = textContent;
} else if (target === 'extra') {
document.getElementById('sourceExtraText').value = textContent;
}
alert('تم استخدام محتوى Excel بنجاح');
}
/* =====================================
دالة توحيد الأرقام بمختلف أشكالها
===================================== */
function normalizeNumbers(text) {
// تحويل الأرقام العربية والهندية إلى أرقام إنجليزية
return text.replace(/[٠١٢٣٤٥٦٧٨٩]/g, d => d.charCodeAt(0) - 1632) // أرقام عربية
.replace(/[۰۱۲۳۴۵۶۷۸۹]/g, d => d.charCodeAt(0) - 1776); // أرقام هندية
}
/* =====================================
تقسيم النص إلى جمل - محسن
===================================== */
function splitIntoSentences(text) {
// تعبير منتظم محسن للتعامل مع نهايات الجمل بشكل أفضل
const sentences = text.split(/(?<=[.!?])\s+|(?<=\n\s*\n)/g);
// تنظيف النتائج من الجمل الفارغة
return sentences.filter(s => s.trim());
}
/* =====================================
دالة معالجة عملية التحليل عند الضغط على الزر - محسنة مع غير متزامنة
===================================== */
document.getElementById('submitBtn').addEventListener('click', async () => {
try {
let sourceText = document.getElementById('sourceText').value;
let targetText = document.getElementById('targetText').value;
// مسح الرسائل السابقة وإظهار النتائج
document.getElementById('errorsList').innerHTML = '';
document.getElementById('resultSection').classList.remove('hidden');
if (!sourceText || !targetText) {
addError('يرجى إدخال كلا النصين المصدر والهدف');
return;
}
// عرض مؤشر التحميل
showLoadingIndicator('جاري تحضير النصوص للتحليل...', '0%');
// تطبيق التصحيح على النصوص إذا كانت مستندات رسمية
if (selectedFileType === 'official') {
addError('جارٍ تصحيح النصوص للمستندات الرسمية...', 'info');
updateLoadingProgress('جاري تصحيح النص المصدر...', '5%');
// تصحيح النص المصدر إذا لم يتم تصحيحه من قبل
if (!correctionApplied) {
sourceText = await correctOfficialDocument(sourceText, 'source');
document.getElementById('sourceText').value = sourceText;
}
updateLoadingProgress('جاري تصحيح النص الهدف...', '20%');
// تصحيح النص الهدف
targetText = await correctOfficialDocument(targetText, 'target');
document.getElementById('targetText').value = targetText;
addError('تم تصحيح النصوص بنجاح', 'info');
}
addError('جارٍ تقسيم النصوص ومزامنتها...', 'info');
updateLoadingProgress('جاري تقسيم النصوص ومزامنتها...', '30%');
// تقسيم النصوص إلى أجزاء متزامنة بحد أقصى 6 أقسام
const segments = await alignTextsWithModel(sourceText, targetText);
if (!segments || segments.length === 0) {
throw new Error('فشل في تقسيم النصوص - لم يتم العثور على أقسام');
}
// عرض الأقسام في المسودة مع التمييز المبدئي
displayDraftSegments(segments);
// تخزين الأقسام في المتغير العام
analysisSegments = segments.map(segment => ({
source: segment.source,
target: segment.target,
analysis: 'جارٍ التحليل...',
errors: { numbers: 0, missing: 0, meaning: 0 }
}));
addError(`تم تقسيم النصوص إلى ${segments.length} قسم بنجاح`, 'info');
addError('جارٍ تحليل الأقسام...', 'info');
updateLoadingProgress('جاري تحليل الأقسام...', '50%');
let totalNumberErrors = 0;
let totalMissingErrors = 0;
let totalMeaningErrors = 0;
// تحليل كل قسم على حدة
for (let i = 0; i < segments.length; i++) {
updateLoadingProgress(`جاري تحليل القسم ${i+1} من ${segments.length}...`,
`${Math.round(50 + ((i + 1) / segments.length) * 40)}%`);
// استدعاء دالة تحليل القسم
const analysisResult = await analyzeAlignedPair(segments[i].source, segments[i].target, i+1);
// تحديث المسودة بنتائج التحليل
updateSegmentAnalysis(i, analysisResult);
// تحديث متغير تخزين التحليل
analysisSegments[i].analysis = analysisResult.analysis;
analysisSegments[i].errors = analysisResult.errors;
// تجميع إجمالي الأخطاء
totalNumberErrors += analysisResult.errors.numbers;
totalMissingErrors += analysisResult.errors.missing;
totalMeaningErrors += analysisResult.errors.meaning;
// السماح بوقت للمعالجة بين الطلبات
await new Promise(resolve => setTimeout(resolve, 200));
}
updateLoadingProgress('جاري تحضير النتائج النهائية...', '95%');
// إعداد الملخص النهائي
const totalErrors = totalNumberErrors + totalMissingErrors + totalMeaningErrors;
if (totalErrors === 0) {
addError('لم يتم العثور على أخطاء - النصوص متطابقة', 'info');
} else {
addError(`تم الانتهاء من التحليل. العثور على ${totalErrors} خطأ:`, 'info');
if (totalNumberErrors > 0) addError(`- ${totalNumberErrors} اختلاف في الأرقام`, 'warning');
if (totalMissingErrors > 0) addError(`- ${totalMissingErrors} نص مفقود`, 'warning');
if (totalMeaningErrors > 0) addError(`- ${totalMeaningErrors} اختلاف في المعنى`, 'warning');
}
// تجهيز العرض المقسم
displaySegmentedView();
// تجهيز العرض التفاعلي
setupInteractiveView();
// تحديث خريطة الحرارة
updateHeatMap();
updateLoadingProgress('تمت العملية بنجاح!', '100%');
setTimeout(() => {
hideLoadingIndicator();
}, 1000);
} catch (error) {
console.error('Error during analysis:', error);
addError('حدث خطأ أثناء التحليل: ' + error.message, 'error');
hideLoadingIndicator();
}
});
/* =====================================
دالة إضافة التمييز المبدئي للنص
===================================== */
function addPreliminaryHighlights(sourceText, targetText) {
let highlightedText = sourceText;
// 1. تحديد الأرقام المحتملة
const numberRegex = /(\d+|[٠١٢٣٤٥٦٧٨٩]+)/g;
highlightedText = highlightedText.replace(numberRegex, '<span class="preliminary-highlight-number">\$1</span>');
// 2. تحديد النصوص المفقودة المحتملة
const paragraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim().length > 20);
for (const paragraph of paragraphs) {
if (!isTextSubstantiallyIncluded(paragraph, targetText)) {
highlightedText = highlightedText.replace(
paragraph,
`<span class="preliminary-highlight-missing">${paragraph}</span>`
);
}
}
return highlightedText;
}
/* =====================================
التحقق من تضمين النص بشكل جوهري
===================================== */
function isTextSubstantiallyIncluded(text, targetText) {
// استخراج الكلمات المهمة (أطول من 3 أحرف)
const words = text.split(/\s+/).filter(w => w.length > 3);
if (words.length === 0) return true;
// حساب عدد الكلمات المهمة الموجودة في النص الهدف
const foundCount = words.filter(word => targetText.includes(word)).length;
// اعتبار النص مضمن إذا وجدنا أكثر من 50% من الكلمات المهمة
return foundCount / words.length > 0.5;
}
/* =====================================
دالة تقسيم النصوص مع عدد أقصى 6 أقسام - محسنة غير متزامنة
===================================== */
async function alignTextsWithModel(sourceText, targetText) {
try {
// إظهار حالة التقسيم
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري تقسيم ومزامنة النصوص...';
document.getElementById('progressBar').style.width = '30%';
updateLoadingProgress('جاري تقسيم ومزامنة النصوص...', '35%');
// استدعاء نموذج DeepSeek للتقسيم والمزامنة
const prompt = `مهمتك هي تقسيم النصين التاليين (النص المصدر والنص الهدف) إلى أقسام متوازية بحيث يتطابق كل قسم في المصدر مع ما يقابله في الهدف.
ملاحظات مهمة:
1. عدد الأقسام يجب أن لا يتجاوز 6 أقسام بأي حال
2. قسّم النص إلى أجزاء منطقية ومترابطة
3. حاول الحفاظ على تكامل الفقرات والجمل
أعِد النتائج بصيغة JSON بهذا الشكل:
{
"segments": [
{
"source": "نص المصدر للقسم الأول",
"target": "نص الهدف المقابل للقسم الأول"
},
...
]
}
النص المصدر:
${sourceText}
النص الهدف:
${targetText}`;
const payload = {
model: "deepseek-chat",
messages: [
{ role: "system", content: "أنت خبير في تقسيم النصوص ومزامنتها للمقارنة والتحليل." },
{ role: "user", content: prompt }
],
temperature: 0.3,
max_tokens: 8000
};
document.getElementById('progressBar').style.width = '50%';
updateLoadingProgress('جاري معالجة تقسيم النصوص...', '40%');
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('فشل استدعاء نموذج DeepSeek: ' + response.statusText);
}
document.getElementById('progressBar').style.width = '80%';
updateLoadingProgress('جاري تحليل النتائج...', '45%');
const data = await response.json();
const result = data.choices[0].message.content;
// استخراج JSON من النتيجة
let segments = [];
try {
const jsonMatch = result.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const parsedData = JSON.parse(jsonMatch[0]);
segments = parsedData.segments || [];
}
} catch (e) {
console.error('خطأ في تحليل نتيجة تقسيم النصوص:', e);
// استخدام طريقة بديلة إذا فشل التحليل
segments = createFallbackSegments(sourceText, targetText);
}
// لضمان عدم تخطي الحد الأقصى 6 أقسام
if (segments.length > 6) {
segments = consolidateSegments(segments, 6);
}
document.getElementById('progressBar').style.width = '100%';
document.getElementById('statusText').textContent = 'تم تقسيم النصوص بنجاح!';
updateLoadingProgress('تم تقسيم النصوص بنجاح!', '50%');
// إخفاء حالة المعالجة بعد فترة قصيرة
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
}, 1000);
return segments;
} catch (error) {
console.error('خطأ في تقسيم النصوص:', error);
// إخفاء حالة المعالجة وإظهار رسالة الخطأ
document.getElementById('processingStatus').classList.add('hidden');
addError('حدث خطأ أثناء تقسيم النصوص: ' + error.message);
// إعادة تقسيم بسيط كحل احتياطي مع تحديد أقصى 6 أقسام
return createFallbackSegments(sourceText, targetText);
}
}
/* =====================================
دمج الأقسام لتقليل عددها إلى الحد الأقصى
===================================== */
function consolidateSegments(segments, maxSegments) {
if (segments.length <= maxSegments) {
return segments;
}
// عدد الأقسام في كل مجموعة جديدة
const segmentsPerGroup = Math.ceil(segments.length / maxSegments);
const consolidatedSegments = [];
for (let i = 0; i < segments.length; i += segmentsPerGroup) {
// دمج مجموعة من الأقسام معًا
const groupSegments = segments.slice(i, Math.min(i + segmentsPerGroup, segments.length));
const consolidatedSource = groupSegments.map(s => s.source).join('\n\n');
const consolidatedTarget = groupSegments.map(s => s.target).join('\n\n');
consolidatedSegments.push({
source: consolidatedSource,
target: consolidatedTarget
});
}
return consolidatedSegments;
}
/* =====================================
إنشاء تقسيمات احتياطية - محدودة بـ 6 أقسام
===================================== */
function createFallbackSegments(sourceText, targetText) {
addError('تم الانتقال إلى آلية التقسيم الاحتياطية', 'warning');
// تقسيم النصوص إلى فقرات
const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim());
const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim());
// حساب عدد الأقسام المطلوبة (6 كحد أقصى)
const MAX_SEGMENTS = 6;
const sourceSegmentsCount = Math.min(MAX_SEGMENTS, sourceParagraphs.length);
const segmentsPerGroup = Math.ceil(sourceParagraphs.length / sourceSegmentsCount);
const segments = [];
// توزيع فقرات المصدر على الأقسام
for (let i = 0; i < sourceSegmentsCount; i++) {
const startIdx = i * segmentsPerGroup;
const endIdx = Math.min(startIdx + segmentsPerGroup, sourceParagraphs.length);
const sourceSegment = sourceParagraphs.slice(startIdx, endIdx).join('\n\n');
// محاولة العثور على أقسام مقابلة في الهدف
let targetSegment = '';
// تخمين الأقسام المقابلة في الهدف بناءً على الطول النسبي
const targetStartIdx = Math.floor((startIdx / sourceParagraphs.length) * targetParagraphs.length);
const targetEndIdx = Math.min(Math.floor((endIdx / sourceParagraphs.length) * targetParagraphs.length), targetParagraphs.length);
targetSegment = targetParagraphs.slice(targetStartIdx, targetEndIdx).join('\n\n');
// إذا لم نجد قسمًا مقابلًا، نضع رسالة توضيحية
if (!targetSegment) {
targetSegment = '(لا يوجد نص مقابل في الهدف)';
}
segments.push({
source: sourceSegment,
target: targetSegment
});
}
return segments;
}
/* =====================================
تحليل قسم واحد باستخدام نموذج الذكاء الاصطناعي - محسنة غير متزامنة
===================================== */
async function analyzeAlignedPair(sourceText, targetText, pairNumber) {
try {
// إظهار حالة التحليل
document.getElementById('statusText').textContent = `جاري تحليل القسم ${pairNumber}...`;
updateLoadingProgress(`جاري تحليل القسم ${pairNumber}...`,
`${Math.round(50 + ((pairNumber) / analysisSegments.length) * 40)}%`);
// برومبت محسن للتحليل
const prompt = `قارن النص المصدر والنص الهدف التاليين، وحدد بدقة:
1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي < > مع الكلمة السابقة والكلمة اللاحقة أو الجملة كاملة
2. النصوص المفقودة: ضع النص المفقود في الترجمة بين علامتي __ __
3. اختلافات المعنى: ضع النص الذي تغير معناه بين علامتي [MEANING] [/MEANING]
اعتبر الأرقام بمختلف أنظمتها (العربية والهندية والإنجليزية) متطابقة إذا كانت تمثل نفس الرقم.
الجمل عادةً تكون من 20 إلى 300 كلمة. النصوص عبارة عن نص سورس ونص تاجرت واحد أصلي والثاني ترجمته.
استخراج النصوص المفقودة حيث يوجد في المصدر جملة أو فقرة أو كلمة (بالعربي أو الإنجليزي) وفي النص المقابل لا يوجد ما يعادلها في المعنى بأي لغة.
قم بتحليل كل جملة على حدة وتحديد الاختلافات بدقة.
لكل اختلاف، قدم شرحاً موجزاً للخطأ والتصحيح المقترح.
النص المصدر:
${sourceText}
النص الهدف:
${targetText}
`;
const payload = {
model: "deepseek-chat",
messages: [
{ role: "system", content: "أنت خبير لغوي في تحليل ومقارنة النصوص المترجمة بدقة عالية." },
{ role: "user", content: prompt }
],
temperature: 0.2,
max_tokens: 2048
};
// استدعاء API
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('فشل استدعاء API للتحليل: ' + response.statusText);
}
const data = await response.json();
const analysisResult = data.choices[0].message.content.trim();
// تحليل الاختلافات وتصنيفها
const numberDiffs = (analysisResult.match(/<[^<>]+>/g) || []).length;
const missingTexts = (analysisResult.match(/__(.*?)__/g) || []).length;
const meaningDiffs = (analysisResult.match(/\[MEANING\](.*?)\[\/MEANING\]/g) || []).length;
const totalDiffs = numberDiffs + missingTexts + meaningDiffs;
// إنشاء ملخص للتحليل
let summary;
if (totalDiffs === 0) {
summary = '<span class="perfect-match">لم يتم العثور على اختلافات. النصوص متطابقة.</span>';
} else {
summary = `تم العثور على `;
const parts = [];
if (numberDiffs > 0) parts.push(`<span class="highlight-number">${numberDiffs} اختلاف في الأرقام</span>`);
if (missingTexts > 0) parts.push(`<span class="highlight-missing">${missingTexts} نص مفقود</span>`);
if (meaningDiffs > 0) parts.push(`<span class="highlight-meaning">${meaningDiffs} اختلاف في المعنى</span>`);
summary += parts.join('، ') + ".";
}
return {
pairId: pairNumber,
sourceText: sourceText,
targetText: targetText,
analysis: analysisResult,
summary: summary,
errors: {
numbers: numberDiffs,
missing: missingTexts,
meaning: meaningDiffs
}
};
} catch (error) {
console.error(`خطأ في تحليل القسم ${pairNumber}:`, error);
return {
pairId: pairNumber,
sourceText: sourceText,
targetText: targetText,
analysis: "حدث خطأ في التحليل: " + error.message,
summary: "فشل التحليل",
errors: { numbers: 0, missing: 0, meaning: 0 }
};
}
}
/* =====================================
عرض الأقسام في المسودة - محسن مع تمييز مبدئي
===================================== */
function displayDraftSegments(segments) {
const container = document.getElementById('paragraphDivisionsContainer');
container.innerHTML = ''; // مسح المحتوى الحالي
segments.forEach((segment, index) => {
// إنشاء عنصر القسم القابل للطي
const sectionDiv = document.createElement('div');
sectionDiv.className = 'collapsible-section';
// إنشاء الترويسة مع إضافة زر إعادة التحليل
const headerDiv = document.createElement('div');
headerDiv.className = 'section-header';
headerDiv.innerHTML = `
<div class="flex items-center">
<span class="draft-marker">مسودة</span>
<span>القسم ${index+1}</span>
<div class="reanalyze-btn" title="إعادة تحليل هذا القسم فقط" data-segment-index="${index}">
<i class="fas fa-sync-alt"></i>
</div>
</div>
<i class="fas fa-chevron-down"></i>
`;
// إضافة تمييز مبدئي للنص المصدر
const preliminaryHighlightedSource = addPreliminaryHighlights(segment.source, segment.target);
// إنشاء محتوى القسم بطريقة محسنة
const contentDiv = document.createElement('div');
contentDiv.className = 'section-content';
// استخدام تنسيق محسن للفقرات المتوازية مع التمييز المبدئي
contentDiv.innerHTML = `
<div class="aligned-paragraphs mb-4">
<div class="paragraph-ar">${preliminaryHighlightedSource}</div>
<div class="paragraph-en">${segment.target}</div>
</div>
<div id="analysis-segment-${index}" class="paragraph-section">
<h4 class="font-bold text-gray-700 mb-2">التحليل:</h4>
<div class="bg-gray-50 p-3 rounded-lg flex items-center justify-center">
<div class="animate-spin h-5 w-5 border-4 border-blue-600 rounded-full border-t-transparent ml-2"></div>
<span>جارٍ التحليل...</span>
</div>
</div>
`;
// إضافة الأقسام إلى الحاوية
sectionDiv.appendChild(headerDiv);
sectionDiv.appendChild(contentDiv);
container.appendChild(sectionDiv);
// إضافة مستمع الحدث للنقر على الترويسة
headerDiv.addEventListener('click', function() {
contentDiv.classList.toggle('open');
const icon = headerDiv.querySelector('i.fas');
icon.classList.toggle('fa-chevron-down');
icon.classList.toggle('fa-chevron-up');
});
});
// إظهار قسم المسودة
document.getElementById('fullTextDraftSection').classList.remove('hidden');
}
/* =====================================
تحديث تحليل القسم في المسودة - محسن
===================================== */
function updateSegmentAnalysis(segmentIndex, analysisResult) {
const analysisContainer = document.getElementById(`analysis-segment-${segmentIndex}`);
if (analysisContainer) {
// تحسين طريقة عرض نتائج التحليل
analysisContainer.innerHTML = `
<h4 class="font-bold text-gray-700 mb-2">التحليل:</h4>
<div class="bg-gray-50 p-3 rounded-lg">
${formatAnalysisText(analysisResult.analysis)}
</div>
<div class="mt-3 p-3 rounded-lg bg-blue-50 border-r-4 border-blue-400">
<span class="font-bold block mb-1">الملخص:</span> ${analysisResult.summary}
</div>
<div class="mt-3 grid grid-cols-3 gap-2 text-sm">
<div class="bg-yellow-50 p-2 rounded-lg border border-yellow-200 flex flex-col items-center">
<span class="font-bold">اختلافات الأرقام</span>
<span class="text-xl font-bold mt-1 ${analysisResult.errors.numbers > 0 ? 'text-yellow-600' : 'text-green-600'}">
${analysisResult.errors.numbers}
</span>
</div>
<div class="bg-blue-50 p-2 rounded-lg border border-blue-200 flex flex-col items-center">
<span class="font-bold">نصوص مفقودة</span>
<span class="text-xl font-bold mt-1 ${analysisResult.errors.missing > 0 ? 'text-blue-600' : 'text-green-600'}">
${analysisResult.errors.missing}
</span>
</div>
<div class="bg-red-50 p-2 rounded-lg border border-red-200 flex flex-col items-center">
<span class="font-bold">اختلافات المعنى</span>
<span class="text-xl font-bold mt-1 ${analysisResult.errors.meaning > 0 ? 'text-red-600' : 'text-green-600'}">
${analysisResult.errors.meaning}
</span>
</div>
</div>
`;
}
}
/* =====================================
دالة تمييز النص - محسنة لتمييز الجمل الكاملة
===================================== */
function applyHighlights(originalText, targetText, analysisOutput) {
// تقسيم النص الأصلي إلى جمل
const sentences = splitIntoSentences(originalText);
// تطبيق تمييز للنص المفقود تماما والمفقود جزئيا
const enhancedSourceText = highlightMissingText(originalText, targetText);
// استخراج النصوص التي بها مشاكل من التحليل
const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g)).map(m => m[1].trim());
const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g)).map(m => m[1].trim());
const meaningMatches = Array.from(analysisOutput.matchAll(/$$MEANING$$(.*?)$$\/MEANING$$/g)).map(m => m[1].trim());
// معالجة كل جملة على حدة وتطبيق التحديد
const highlightedText = sentences.map((sentence, index) => {
let hasError = false;
let highlightedSentence = sentence;
let errorType = '';
// تحديد اختلافات الأرقام
for (const phrase of numberMatches) {
if (sentence.includes(phrase)) {
const regex = new RegExp(escapeRegExp(phrase), 'g');
highlightedSentence = highlightedSentence.replace(regex, `<span class="highlight-number" data-error-type="number" data-explanation="تأكد من صحة الرقم في النص الهدف وتطابقه مع النص المصدر">${phrase}</span>`);
hasError = true;
errorType = 'number';
}
}
// تحديد النصوص المفقودة
for (const phrase of missingMatches) {
if (sentence.includes(phrase)) {
const regex = new RegExp(escapeRegExp(phrase), 'g');
highlightedSentence = highlightedSentence.replace(regex, `<span class="highlight-missing" data-error-type="missing" data-explanation="أضف النص المفقود إلى الترجمة للحفاظ على اكتمال المعنى">${phrase}</span>`);
hasError = true;
errorType = errorType || 'missing';
}
}
// تحديد اختلافات المعنى
for (const phrase of meaningMatches) {
if (sentence.includes(phrase)) {
const regex = new RegExp(escapeRegExp(phrase), 'g');
highlightedSentence = highlightedSentence.replace(regex, `<span class="highlight-meaning" data-error-type="meaning" data-explanation="راجع الترجمة للتأكد من نقل المعنى الصحيح دون تحريف">${phrase}</span>`);
hasError = true;
errorType = errorType || 'meaning';
}
}
// إذا كان هناك خطأ، قم بتمييز الجملة كاملة
if (hasError) {
return `<span class="sentence-with-error" data-error-id="${index}" data-error-type="${errorType}" data-sentence-number="${index+1}">${highlightedSentence}</span>`;
}
return highlightedSentence;
}).join(' ');
return highlightedText;
}
/* =====================================
دالة تمييز النص المفقود
===================================== */
function highlightMissingText(sourceText, targetText) {
// تقسيم النصوص إلى فقرات
const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim());
const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim());
// مصفوفة لتخزين النص المعدل
let modifiedSource = sourceText;
// البحث عن الفقرات المفقودة تماما في الهدف
for (const paragraph of sourceParagraphs) {
if (paragraph.trim().length < 10) continue; // تجاهل الفقرات القصيرة جدا
// فحص ما إذا كانت الفقرة مفقودة تماما
let isCompletelyMissing = true;
// البحث عن أجزاء من الفقرة في النص الهدف
for (const targetPara of targetParagraphs) {
if (targetPara.includes(paragraph) || paragraph.includes(targetPara)) {
isCompletelyMissing = false;
break;
}
}
// إذا كانت مفقودة تماما، نميزها بشكل مختلف
if (isCompletelyMissing) {
modifiedSource = modifiedSource.replace(
paragraph,
`<span class="completely-missing" title="هذا النص مفقود تماما في الترجمة">${paragraph}</span>`
);
}
}
// البحث عن الجمل المفقودة جزئيا
const sourceLines = sourceParagraphs.join('\n').split(/[.!?]\s+/);
for (const line of sourceLines) {
if (line.trim().length < 5) continue; // تجاهل الجمل القصيرة جدا
// البحث عن جمل موجودة في السورس ومفقودة في التارجت
let isPartiallyMissing = true;
if (targetText.includes(line)) {
isPartiallyMissing = false;
} else {
// البحث عن كلمات رئيسية من الجملة في التارجت
const keywords = line.split(/\s+/).filter(word => word.length > 3);
let foundKeywords = 0;
for (const keyword of keywords) {
if (targetText.includes(keyword)) {
foundKeywords++;
}
}
// إذا وجدنا أكثر من 50% من الكلمات الرئيسية، فهي مفقودة جزئيا
if (keywords.length > 0 && foundKeywords / keywords.length > 0.5) {
isPartiallyMissing = false;
}
}
// تمييز الجمل المفقودة جزئيا فقط إذا لم تكن مميزة بالفعل كمفقودة تماما
if (isPartiallyMissing && !modifiedSource.includes(`<span class="completely-missing"`)) {
modifiedSource = modifiedSource.replace(
line,
`<span class="partially-missing" title="هذا النص موجود بشكل جزئي في الترجمة">${line}</span>`
);
}
}
return modifiedSource;
}
/* =====================================
عرض النتائج في العرض المقسم - محسن
===================================== */
function displaySegmentedView() {
const container = document.getElementById('segmentedComparisonContainer');
container.innerHTML = ''; // مسح المحتوى الحالي
// إضافة مقطع لكل قسم
analysisSegments.forEach((segment, index) => {
// تحديد أنواع الأخطاء في هذا القسم
const hasNumbers = segment.errors.numbers > 0;
const hasMissing = segment.errors.missing > 0;
const hasMeaning = segment.errors.meaning > 0;
// إنشاء علامات الأخطاء
let tagHTML = '';
if (hasNumbers) {
tagHTML += '<span class="segment-tag tag-error">أخطاء أرقام</span>';
}
if (hasMissing) {
tagHTML += '<span class="segment-tag tag-warning">نصوص مفقودة</span>';
}
if (hasMeaning) {
tagHTML += '<span class="segment-tag tag-info">اختلاف معنى</span>';
}
if (!hasNumbers && !hasMissing && !hasMeaning) {
tagHTML = '<span class="segment-tag tag-success">مطابق</span>';
}
// إنشاء عنصر المقطع
const segmentDiv = document.createElement('div');
segmentDiv.className = 'segment-comparison';
segmentDiv.dataset.segmentIndex = index;
// تطبيق التحديد على نصوص المقطع
const sourceHighlighted = applyHighlights(segment.source, segment.target, segment.analysis);
const targetHighlighted = applyHighlights(segment.target, segment.source, segment.analysis);
// إعداد HTML للمقطع
segmentDiv.innerHTML = `
<div class="segment-header">
<div>المقطع ${index+1}</div>
<div>${tagHTML}</div>
</div>
<div class="segment-content">
<div class="segment-source">${sourceHighlighted}</div>
<div class="segment-target">${targetHighlighted}</div>
</div>
<div class="segment-notes">
${formatAnalysisText(segment.analysis)}
</div>
`;
container.appendChild(segmentDiv);
});
// إضافة شرح للعرض المقسم
document.getElementById('segmentViewExplanation').innerHTML = generateSegmentViewExplanation();
// إضافة مستمعي الأحداث للتحديدات
setTimeout(() => {
document.querySelectorAll('.segment-source .sentence-with-error').forEach(element => {
element.addEventListener('click', function() {
// الحصول على النص الكامل للجملة
const sentenceText = this.textContent;
// تحديد نوع الخطأ من خلال الفئات داخل الجملة
let errorType = 'general';
let errorSpecificText = '';
// البحث عن الخطأ المحدد داخل الجملة
const numberHighlight = this.querySelector('.highlight-number');
const missingHighlight = this.querySelector('.highlight-missing');
const meaningHighlight = this.querySelector('.highlight-meaning');
if (numberHighlight) {
errorType = 'number';
errorSpecificText = numberHighlight.textContent;
} else if (missingHighlight) {
errorType = 'missing';
errorSpecificText = missingHighlight.textContent;
} else if (meaningHighlight) {
errorType = 'meaning';
errorSpecificText = meaningHighlight.textContent;
}
const sentenceNumber = this.getAttribute('data-sentence-number');
// إعداد أمثلة حسب نوع الخطأ
let examples = null;
if (errorType === 'number') {
examples = {
incorrect: `المادة <span class="highlight-number">٥</span> من القانون، تُطبق الغرامة المقدرة <span class="highlight-number">٤٥٠</span> دينارًا.`,
correct: `المادة <span class="corrected-text">٥</span> من القانون، تُطبق الغرامة المقدرة <span class="corrected-text">٤٥٠</span> دينارًا.`
};
} else if (errorType === 'missing') {
examples = {
incorrect: `اتفق الطرفان على أن يتم تسليم البضائع <span class="highlight-missing">خلال 30 يومًا من توقيع العقد</span>.`,
correct: `اتفق الطرفان على أن يتم تسليم البضائع <span class="corrected-text">خلال 30 يومًا من توقيع العقد</span>.`
};
} else if (errorType === 'meaning') {
examples = {
incorrect: `أقرت المحكمة <span class="highlight-meaning">بإدانة</span> المتهم.`,
correct: `أقرت المحكمة <span class="corrected-text">ببراءة</span> المتهم.`
};
}
// إعداد شرح أكثر ودية مع أمثلة
let explanation = '';
if (errorType === 'number') {
explanation = `يبدو أن هناك اختلافًا في الأرقام في هذه الجملة. الرقم "${errorSpecificText}" في النص المصدر لا يتطابق مع النص الهدف. قد يكون هناك خطأ في نقل الرقم أو تحويله بين أنظمة الأرقام المختلفة (العربية، الإنجليزية، الهندية).`;
} else if (errorType === 'missing') {
explanation = `هناك نص مفقود في الترجمة! النص "${errorSpecificText}" موجود في المصدر ولكن لم يتم ترجمته أو تضمينه في النص الهدف. هذا يؤدي إلى فقدان جزء من المعنى الأصلي.`;
} else if (errorType === 'meaning') {
explanation = `هناك اختلاف مهم في المعنى! النص "${errorSpecificText}" تمت ترجمته بطريقة تغير المعنى الأصلي. قد يكون هناك خطأ في فهم السياق أو اختيار المصطلحات المناسبة.`;
} else {
explanation = `هناك خطأ في هذه الجملة: "${sentenceText}"<br><br>يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`;
}
// استخدام النافذة المنبثقة المحسنة
showEnhancedPopup(errorType, sentenceText, explanation, examples);
});
});
}, 100);
}
/* =====================================
إعداد وعرض العرض التفاعلي - محسن
===================================== */
function setupInteractiveView() {
// استخراج كل الاختلافات وتخزينها
allDifferences = [];
let numberDiffCount = 0;
let missingTextCount = 0;
let meaningDiffCount = 0;
// فحص جميع المقاطع
analysisSegments.forEach((segment, segmentIndex) => {
// استخراج اختلافات الأرقام
const numberMatches = Array.from(segment.analysis.matchAll(/<([^<>]+)>/g));
numberMatches.forEach(match => {
allDifferences.push({
type: 'number',
text: match[1],
segmentIndex: segmentIndex,
context: getContextAroundMatch(segment.source, match[1], 15)
});
numberDiffCount++;
});
// استخراج النصوص المفقودة
const missingMatches = Array.from(segment.analysis.matchAll(/__(.*?)__/g));
missingMatches.forEach(match => {
allDifferences.push({
type: 'missing',
text: match[1],
segmentIndex: segmentIndex,
context: getContextAroundMatch(segment.source, match[1], 15)
});
missingTextCount++;
});
// استخراج اختلافات المعنى
const meaningMatches = Array.from(segment.analysis.matchAll(/$$MEANING$$(.*?)$$\/MEANING$$/g));
meaningMatches.forEach(match => {
allDifferences.push({
type: 'meaning',
text: match[1],
segmentIndex: segmentIndex,
context: getContextAroundMatch(segment.source, match[1], 15)
});
meaningDiffCount++;
});
});
// تحديث عدادات الاختلافات
document.getElementById('numberDiffCount').textContent = numberDiffCount;
document.getElementById('missingTextCount').textContent = missingTextCount;
document.getElementById('meaningDiffCount').textContent = meaningDiffCount;
// إعداد توصيات المعالجة
const recommendationsContainer = document.getElementById('diffRecommendations');
recommendationsContainer.innerHTML = '';
if (allDifferences.length === 0) {
recommendationsContainer.innerHTML = `
<div class="flex items-center justify-center p-4">
<div class="text-center">
<i class="fas fa-check-circle text-green-500 text-4xl mb-2"></i>
<p>لا توجد اختلافات تحتاج إلى معالجة!</p>
</div>
</div>`;
} else {
// إضافة توصيات مخصصة بناءً على أنواع الاختلافات
if (numberDiffCount > 0) {
recommendationsContainer.innerHTML += `<p class="flex items-center"><i class="fas fa-hashtag text-yellow-500 ml-2"></i> راجع الأرقام واحرص على تطابقها بين النصين.</p>`;
}
if (missingTextCount > 0) {
recommendationsContainer.innerHTML += `<p class="flex items-center"><i class="fas fa-plus-circle text-blue-500 ml-2"></i> أضف النصوص المفقودة في الترجمة لضمان اكتمال المحتوى.</p>`;
}
if (meaningDiffCount > 0) {
recommendationsContainer.innerHTML += `<p class="flex items-center"><i class="fas fa-exchange-alt text-red-500 ml-2"></i> صحح اختلافات المعنى لضمان دقة الترجمة.</p>`;
}
// توصية عامة
recommendationsContainer.innerHTML += `<p class="flex items-center mt-2 pt-2 border-t border-green-200"><i class="fas fa-th-large text-green-500 ml-2"></i> استخدم وضع العرض المقسم للتعديل الدقيق.</p>`;
}
// تعيين أحداث النقر لأزرار التنقل
document.getElementById('prevDiff').addEventListener('click', showPreviousDifference);
document.getElementById('nextDiff').addEventListener('click', showNextDifference);
// إعداد الاختلاف الأول
if (allDifferences.length > 0) {
currentDiffIndex = 0;
document.getElementById('prevDiff').disabled = false;
document.getElementById('nextDiff').disabled = false;
displayCurrentDifference();
} else {
document.getElementById('diffCounter').textContent = "0/0";
document.getElementById('currentDiffDisplay').innerHTML = `
<div class="flex flex-col items-center justify-center py-8">
<div class="text-6xl text-green-300 mb-4">
<i class="fas fa-check-circle"></i>
</div>
<p class="text-gray-500 text-center">لم يتم العثور على اختلافات</p>
<p class="text-green-500 text-center mt-2 font-bold">النصوص متطابقة!</p>
</div>`;
document.getElementById('prevDiff').disabled = true;
document.getElementById('nextDiff').disabled = true;
}
// إضافة توضيحات للعرض التفاعلي
document.getElementById('interactiveViewExplanation').innerHTML = generateInteractiveViewExplanation();
}
/* =====================================
دوال مساعدة للعرض التفاعلي
===================================== */
// الحصول على السياق المحيط بنص معين
function getContextAroundMatch(text, match, contextSize) {
const index = text.indexOf(match);
if (index === -1) return "";
// الحصول على جملة كاملة تحتوي على النص المطابق
const start = Math.max(0, text.lastIndexOf('.', index) + 1);
const end = Math.min(text.length, text.indexOf('.', index + match.length) + 1);
// إذا لم نتمكن من العثور على جملة كاملة، نستخدم عدد الأحرف
let context;
if (end - start < 10) {
const altStart = Math.max(0, index - contextSize);
const altEnd = Math.min(text.length, index + match.length + contextSize);
context = text.substring(altStart, altEnd);
} else {
context = text.substring(start, end);
}
// تمييز النص المطابق
const highlightedContext = context.replace(
new RegExp(`(${escapeRegExp(match)})`, 'g'),
`<span class="font-bold text-blue-700 bg-blue-100 px-1 rounded">\$1</span>`
);
return highlightedContext;
}
// عرض الاختلاف السابق
function showPreviousDifference() {
if (allDifferences.length === 0) return;
currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % allDifferences.length;
displayCurrentDifference();
}
// عرض الاختلاف التالي
function showNextDifference() {
if (allDifferences.length === 0) return;
currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length;
displayCurrentDifference();
}
// عرض الاختلاف الحالي
function displayCurrentDifference() {
if (allDifferences.length === 0 || currentDiffIndex < 0) {
document.getElementById('currentDiffDisplay').innerHTML =
'<p class="text-gray-500 text-center">اضغط "التالي" للبدء في استعراض الاختلافات</p>';
document.getElementById('diffCounter').textContent = "0/0";
document.getElementById('diffDetailedView').classList.add('hidden');
document.getElementById('diffReference').classList.add('hidden');
return;
}
const diff = allDifferences[currentDiffIndex];
const segment = analysisSegments[diff.segmentIndex];
let typeLabel = '';
let typeClass = '';
let icon = '';
if (diff.type === 'number') {
typeLabel = 'اختلاف رقمي';
typeClass = 'bg-yellow-100 text-yellow-800';
icon = 'fas fa-hashtag';
} else if (diff.type === 'missing') {
typeLabel = 'نص مفقود';
typeClass = 'bg-blue-100 text-blue-800';
icon = 'fas fa-minus-circle';
} else if (diff.type === 'meaning') {
typeLabel = 'اختلاف معنى';
typeClass = 'bg-red-100 text-red-800';
icon = 'fas fa-exclamation-circle';
}
let highlightedContext = diff.context;
// إضافة رقم المقطع ونوع الخطأ وعرض السياق
document.getElementById('currentDiffDisplay').innerHTML = `
<div class="mb-3 flex items-center justify-between">
<div class="flex items-center">
<span class="rounded-full px-3 py-1 text-sm ${typeClass} flex items-center">
<i class="${icon} ml-1"></i>
${typeLabel}
</span>
<span class="mr-2 text-gray-500">المقطع ${diff.segmentIndex + 1}</span>
</div>
<span class="text-sm text-gray-500">${currentDiffIndex + 1}/${allDifferences.length}</span>
</div>
<div class="mb-3 font-bold flex items-center">
<i class="fas fa-quote-right text-gray-400 ml-2"></i>
<span>السياق:</span>
</div>
<div class="bg-gray-50 p-4 rounded-lg mb-3 leading-relaxed">${highlightedContext}</div>
<div class="text-sm text-gray-600">
<div class="font-bold mb-1 flex items-center">
<i class="fas fa-lightbulb text-yellow-500 ml-2"></i>
<span>التوصية:</span>
</div>
<div class="p-2 bg-yellow-50 rounded-lg border-r-3 border-yellow-400">
${getRecommendationForDiff(diff)}
</div>
</div>
`;
document.getElementById('diffCounter').textContent = `${currentDiffIndex + 1}/${allDifferences.length}`;
// عرض النص في المصدر والهدف
document.getElementById('diffDetailedView').classList.remove('hidden');
// عرض النص المصدر
document.getElementById('diffSourceText').innerHTML = `
<div class="p-2 bg-white rounded-lg border border-gray-200 leading-relaxed">
${highlightSourceText(segment.source, diff.text)}
</div>`;
// عرض النص الهدف
document.getElementById('diffTargetText').innerHTML = `
<div class="p-2 bg-white rounded-lg border border-gray-200 leading-relaxed">
${highlightTargetText(segment.target, diff)}
</div>`;
// عرض قسم المراجع إذا كان متاحا
if (diff.reference) {
document.getElementById('diffReference').classList.remove('hidden');
document.getElementById('diffReferenceText').textContent = diff.reference;
} else {
document.getElementById('diffReference').classList.add('hidden');
}
}
// تمييز النص في المصدر
function highlightSourceText(sourceText, diffText) {
return sourceText.replace(
new RegExp(`(${escapeRegExp(diffText)})`, 'g'),
`<mark class="bg-yellow-200 px-1 rounded">\$1</mark>`
);
}
// تمييز النص في الهدف
function highlightTargetText(targetText, diff) {
if (diff.type === 'missing') {
// بالنسبة للنص المفقود، نحاول العثور على مكان يجب إدراجه
const words = diff.text.split(/\s+/).filter(w => w.length > 3);
let markedText = targetText;
// البحث عن كلمات قبل وبعد النص المفقود في السياق
const contextBefore = diff.context.split(diff.text)[0]?.trim();
const contextAfter = diff.context.split(diff.text)[1]?.trim();
if (contextBefore && targetText.includes(contextBefore)) {
const index = targetText.indexOf(contextBefore) + contextBefore.length;
markedText =
targetText.substring(0, index) +
` <mark class="bg-blue-200 px-1 rounded border border-blue-400 border-dashed">[يجب إضافة: ${diff.text}]</mark> ` +
targetText.substring(index);
} else if (contextAfter && targetText.includes(contextAfter)) {
const index = targetText.indexOf(contextAfter);
markedText =
targetText.substring(0, index) +
` <mark class="bg-blue-200 px-1 rounded border border-blue-400 border-dashed">[يجب إضافة: ${diff.text}]</mark> ` +
targetText.substring(index);
} else {
// إذا لم نتمكن من تحديد المكان، نضيف علامة في النهاية
markedText += ` <mark class="bg-blue-200 px-1 rounded border border-blue-400 border-dashed">[نص مفقود: ${diff.text}]</mark>`;
}
return markedText;
}
else if (diff.type === 'number') {
// البحث عن الأرقام في النص الهدف وتحديد الرقم المختلف
const numbers = targetText.match(/\d+/g) || [];
if (numbers.length === 0) return targetText;
let markedText = targetText;
// تحديد الرقم الذي يختلف عن النص المصدر
for (const num of numbers) {
if (num !== diff.text) {
markedText = markedText.replace(
new RegExp(`(${num})`, 'g'),
`<mark class="bg-yellow-200 px-1 rounded">${num}</mark>`
);
}
}
return markedText;
}
else { // meaning
// البحث عن عبارات مشابهة في النص الهدف
const words = diff.text.split(/\s+/).filter(w => w.length > 3);
let markedText = targetText;
for (const word of words) {
if (targetText.includes(word)) {
const regex = new RegExp(`(.{0,10}${escapeRegExp(word)}.{0,10})`, 'g');
markedText = markedText.replace(
regex,
`<mark class="bg-red-200 px-1 rounded">\$1</mark>`
);
}
}
return markedText;
}
}
// الحصول على توصية مخصصة لكل نوع اختلاف - محسنة لتكون أكثر ودية
function getRecommendationForDiff(diff) {
if (diff.type === 'number') {
return 'يا صديقي، يبدو أن هناك خطأ في الرقم. في النص المصدر يظهر الرقم بشكل مختلف عما هو في النص الهدف. تأكد من تطابق الأرقام في النصين واستخدام نفس الصيغة.';
} else if (diff.type === 'missing') {
return 'يا صديقي، هناك نص مفقود في الترجمة! تأكد من إضافة هذا النص لأنه موجود في المصدر ويحتوي على معلومات مهمة للمحافظة على اكتمال المعنى.';
} else if (diff.type === 'meaning') {
return 'يا صديقي، هناك اختلاف في المعنى بين النصين. النص في المصدر يقول شيئًا مختلفًا عما تمت ترجمته. راجع هذا الجزء للتأكد من دقة الترجمة ونقل المعنى الصحيح.';
}
return '';
}
/* =====================================
هروب أحرف Regex الخاصة
===================================== */
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/* =====================================
تنسيق نص التحليل بتمييز الكلمات المهمة
===================================== */
function formatAnalysisText(text) {
// تمييز الكلمات المهمة
text = text.replace(/الأرقام/g, '<span class="font-bold text-blue-600">الأرقام</span>');
text = text.replace(/المفقودة/g, '<span class="font-bold text-blue-600">المفقودة</span>');
text = text.replace(/المعنى/g, '<span class="font-bold text-blue-600">المعنى</span>');
// تحديد علامات التمييز
text = text.replace(/<([^<>]+)>/g, '<span class="highlight-number">&#x3C;\$1&#x3E;</span>');
text = text.replace(/__(.*?)__/g, '<span class="highlight-missing">__\$1__</span>');
text = text.replace(/$$MEANING$$(.*?)$$\/MEANING$$/g, '<span class="highlight-meaning">\$1</span>');
return text;
}
/* =====================================
توليد شرح للعرض المقسم
===================================== */
function generateSegmentViewExplanation() {
return `
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200">
<h4 class="font-bold text-blue-800 mb-2 flex items-center">
<i class="fas fa-info-circle ml-2"></i>
عن العرض المقسم
</h4>
<p class="text-blue-800">
يقوم هذا العرض بتقسيم النص إلى مقاطع متوازية لتسهيل المقارنة بين النص المصدر والترجمة.
انقر على النص المميز للحصول على تفاصيل إضافية حول الاختلاف.
</p>
</div>
<div class="mt-4 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<h4 class="font-bold text-yellow-800 mb-2 flex items-center">
<i class="fas fa-lightbulb ml-2"></i>
نصائح للاستخدام
</h4>
<ul class="list-disc pr-8 text-sm text-yellow-700 space-y-1">
<li>استخدم فلتر الأخطاء في الأعلى لعرض نوع محدد من الاختلافات</li>
<li>انقر على النص المميز للحصول على تفاصيل إضافية</li>
<li>قارن المقاطع بشكل مباشر لتحديد المشكلات بدقة</li>
</ul>
</div>`;
}
/* =====================================
توليد شرح للعرض التفاعلي
===================================== */
function generateInteractiveViewExplanation() {
return `
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200">
<h4 class="font-bold text-blue-800 mb-2 flex items-center">
<i class="fas fa-info-circle ml-2"></i>
عن العرض التفاعلي
</h4>
<p class="text-blue-800">
يقوم هذا العرض بتمكينك من استعراض الاختلافات واحدة تلو الأخرى بطريقة تفاعلية.
استخدم أزرار "التالي" و"السابق" للتنقل بين الاختلافات المختلفة.
</p>
</div>
<div class="mt-4 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
<h4 class="font-bold text-yellow-800 mb-2 flex items-center">
<i class="fas fa-lightbulb ml-2"></i>
مزايا هذا العرض
</h4>
<ul class="list-disc pr-8 text-sm text-yellow-700 space-y-1">
<li>تركيز أفضل على كل اختلاف على حدة</li>
<li>رؤية النص المصدر والهدف معًا لكل اختلاف</li>
<li>الحصول على توصيات محددة لمعالجة كل مشكلة</li>
</ul>
</div>`;
}
/* =====================================
/* =====================================
دالة للتحقق إذا كان النص مضمن جزئيا
===================================== */
function isPartiallyIncluded(text, targetText) {
// تقسيم النص إلى كلمات رئيسية
const keywords = text.split(/\s+/).filter(word => word.length > 3);
let foundKeywords = 0;
// حساب عدد الكلمات الرئيسية الموجودة في النص الهدف
for (const keyword of keywords) {
if (targetText.includes(keyword)) {
foundKeywords++;
}
}
// اعتبار النص مضمن جزئيا إذا وجدنا أكثر من 30% من الكلمات
return keywords.length > 0 && foundKeywords / keywords.length > 0.3;
}
/* =====================================
تنزيل التقرير بصيغة Word
===================================== */
function downloadWordReport() {
// إنشاء محتوى ملف Word
let wordContent = `
<!DOCTYPE html>
<html xmlns:o='urn:schemas-microsoft-com:office:office'
xmlns:w='urn:schemas-microsoft-com:office:word'
xmlns='http://www.w3.org/TR/REC-html40'>
<head>
<meta charset='utf-8'>
<title>تقرير تحليل النصوص - شركة الريحان</title>
<style>
@page {
size: 21cm 29.7cm;
margin: 2cm;
mso-page-orientation: portrait;
}
body {
font-family: 'Arial', sans-serif;
direction: rtl;
text-align: right;
}
h1, h2, h3, h4 {
color: #3b82f6;
}
.header {
text-align: center;
margin-bottom: 20px;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
}
.header h1 {
color: #3b82f6;
margin-bottom: 5px;
}
.segment {
margin-bottom: 30px;
page-break-inside: avoid;
}
.segment-title {
background-color: #f3f4f6;
padding: 5px;
margin-bottom: 10px;
font-weight: bold;
border-right: 4px solid #3b82f6;
}
.source-text, .target-text {
margin-bottom: 10px;
padding: 10px;
border: 1px solid #e5e7eb;
}
.source-text {
background-color: #f0f9ff;
}
.target-text {
background-color: #fdf2f8;
}
.analysis {
margin-top: 10px;
background-color: #fffbeb;
padding: 10px;
border: 1px solid #fcd34d;
}
.error-numbers {
color: #b45309;
}
.error-missing {
color: #1e40af;
}
.error-meaning {
color: #b91c1c;
}
.footer {
text-align: center;
margin-top: 20px;
font-size: 10px;
color: #6b7280;
border-top: 1px solid #ddd;
padding-top: 10px;
}
.summary {
margin: 20px 0;
border: 1px solid #e5e7eb;
padding: 10px;
}
.summary-title {
font-weight: bold;
margin-bottom: 10px;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 5px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 10px 0;
}
table, th, td {
border: 1px solid #e5e7eb;
padding: 5px;
}
th {
background-color: #f3f4f6;
}
</style>
</head>
<body>
<div class="header">
<h1>تقرير تحليل النصوص المترجمة</h1>
<div>شركة الريحان للترجمة المعتمدة</div>
<div>تاريخ التقرير: ${new Date().toLocaleDateString('ar-EG')}</div>
${correctionApplied ? '<div style="color: #22c55e; font-weight: bold;">تم تطبيق تصحيحات المستندات الرسمية</div>' : ''}
</div>
<div class="summary">
<div class="summary-title">ملخص التحليل</div>
<table>
<tr>
<th>نوع الاختلاف</th>
<th>العدد</th>
</tr>`;
// حساب إجمالي الأخطاء
let totalNumbers = 0;
let totalMissing = 0;
let totalMeaning = 0;
analysisSegments.forEach(segment => {
totalNumbers += segment.errors.numbers;
totalMissing += segment.errors.missing;
totalMeaning += segment.errors.meaning;
});
wordContent += `
<tr>
<td>اختلافات الأرقام</td>
<td>${totalNumbers}</td>
</tr>
<tr>
<td>النصوص المفقودة</td>
<td>${totalMissing}</td>
</tr>
<tr>
<td>اختلافات المعنى</td>
<td>${totalMeaning}</td>
</tr>
<tr>
<td><strong>إجمالي الاختلافات</strong></td>
<td><strong>${totalNumbers + totalMissing + totalMeaning}</strong></td>
</tr>
</table>
</div>`;
// إضافة تفاصيل كل قسم
analysisSegments.forEach((segment, index) => {
wordContent += `
<div class="segment">
<div class="segment-title">القسم ${index + 1}</div>
<div class="source-text">
<strong>النص المصدر:</strong><br>
${segment.source}
</div>
<div class="target-text">
<strong>النص الهدف:</strong><br>
${segment.target}
</div>
<div class="analysis">
<strong>التحليل:</strong><br>
${segment.analysis.replace(/<([^<>]+)>/g, '<span class="error-numbers">&#x3C;$1&#x3E;</span>')
.replace(/__(.*?)__/g, '<span class="error-missing">__$1__</span>')
.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '<span class="error-meaning">$1</span>')}
</div>
</div>`;
});
// إضافة التذييل
wordContent += `
<div class="footer">
تم إنشاء هذا التقرير تلقائيًا بواسطة نظام المراجع الذكي - جميع الحقوق محفوظة لشركة فاست برو للبرمجيات والذكاء الاصطناعي © ${new Date().getFullYear()}
</div>
</body>
</html>`;
// تنزيل الملف
const blob = new Blob(['\ufeff', wordContent], { type: 'application/msword' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'تقرير_تحليل_النصوص.doc';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/* =====================================
عرض الأخطاء والرسائل - محسنة
===================================== */
function addError(message, type = 'error') {
const errorsList = document.getElementById('errorsList');
if (!errorsList) return;
const errorDiv = document.createElement('div');
// تحسين مظهر رسائل الخطأ
let bgColor, borderColor, textColor, icon;
if (type === 'error') {
bgColor = 'bg-red-50';
borderColor = 'border-red-200';
textColor = 'text-red-700';
icon = 'exclamation-circle text-red-500';
} else if (type === 'info') {
bgColor = 'bg-blue-50';
borderColor = 'border-blue-200';
textColor = 'text-blue-700';
icon = 'info-circle text-blue-500';
} else { // warning
bgColor = 'bg-yellow-50';
borderColor = 'border-yellow-200';
textColor = 'text-yellow-700';
icon = 'exclamation-triangle text-yellow-500';
}
errorDiv.className = `p-4 rounded-xl ${bgColor} ${textColor} border ${borderColor} shadow-sm mb-3 animate-scale`;
errorDiv.innerHTML = `
<div class="flex items-center">
<i class="fas fa-${icon} text-xl ml-3"></i>
<span class="font-medium">${message}</span>
<button class="mr-auto text-gray-400 hover:text-gray-600 transition-colors">
<i class="fas fa-times"></i>
</button>
</div>`;
// إضافة زر إغلاق للرسالة
const closeBtn = errorDiv.querySelector('button');
closeBtn.addEventListener('click', () => {
errorDiv.style.opacity = '0';
errorDiv.style.transform = 'translateY(-10px)';
errorDiv.style.transition = 'opacity 0.3s, transform 0.3s';
setTimeout(() => {
errorsList.removeChild(errorDiv);
}, 300);
});
errorsList.appendChild(errorDiv);
// تمرير تلقائي إلى أحدث رسالة
errorDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
/* =====================================
تطبيق فلتر الأخطاء على جميع طرق العرض
===================================== */
function applyErrorFilter(filterType) {
// تطبيق الفلتر على العرض الكلاسيكي
if (filterType === 'all') {
// إظهار كل الأخطاء
document.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => {
el.style.display = '';
});
// إعادة تفعيل جميع الجمل
document.querySelectorAll('.sentence-with-error').forEach(el => {
el.classList.remove('opacity-50');
});
} else {
// إخفاء كل الأخطاء أولاً
document.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => {
el.style.display = 'none';
});
// إضفاء شفافية على جميع الجمل
document.querySelectorAll('.sentence-with-error').forEach(el => {
el.classList.add('opacity-50');
});
// إظهار الأخطاء المطلوبة فقط
let selector = '';
if (filterType === 'number') selector = '.highlight-number';
else if (filterType === 'missing') selector = '.highlight-missing, .completely-missing, .partially-missing';
else if (filterType === 'meaning') selector = '.highlight-meaning';
document.querySelectorAll(selector).forEach(el => {
el.style.display = '';
// إزالة الشفافية عن الجمل المتأثرة
const errorId = el.getAttribute('data-error-id');
if (errorId) {
document.querySelectorAll(`.sentence-with-error[data-error-id="${errorId}"]`).forEach(sentence => {
sentence.classList.remove('opacity-50');
});
}
});
}
// تطبيق الفلتر على العرض المقسم والتفاعلي
// (الرمز يشبه العرض الكلاسيكي مع تعديلات بسيطة للتناسب مع هيكل هذه العروض)
}
</script>
</body>
</html>