rehan / indexproo.html
joermd's picture
Update indexproo.html
a895f7f 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;
}
</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>
</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>
<!-- إحصائيات 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="classicView">
<i class="fas fa-columns"></i> العرض الكلاسيكي
</div>
<div class="view-btn" 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>
<!-- 1. طريقة العرض الكلاسيكية - محسنة -->
<div id="classicView" class="view-container active classic-view-improved">
<div class="result-section split-view">
<!-- عرض النص المصدر بعد التحديد -->
<div>
<h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center source-title">
النص المصدر (مع التعليم)
</h4>
<div id="sourceTextReview" class="min-h-[200px] text-comparison"></div>
</div>
<!-- عرض النص الهدف بعد التحديد -->
<div>
<h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center target-title">
النص الهدف (مع التعليم)
</h4>
<div id="targetTextReview" class="min-h-[200px] text-comparison"></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="classicViewExplanation">
<!-- سيتم تعبئته بواسطة JavaScript -->
</div>
</div>
</div>
<!-- 2. طريقة العرض المقسمة - محسنة -->
<div id="segmentView" class="view-container">
<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="errorPopup" class="error-popup">
<div class="error-popup-header">
<span id="errorPopupTitle">تفاصيل الخطأ</span>
<button class="error-popup-close">&times;</button>
</div>
<div id="errorPopupContent" class="error-popup-body">
<!-- سيتم إضافة محتوى شرح الخطأ هنا بواسطة JavaScript -->
</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>
<!-- حقوق الملكية - إضافة جديدة -->
<footer class="copyright">
<p>جميع الحقوق محفوظة لشركة فاست برو للبرمجيات والذكاء الاصطناعي &copy; <span id="currentYear"></span></p>
<script>document.getElementById('currentYear').textContent = new Date().getFullYear();</script>
</footer>
</main>
</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;
/* =====================================
تهيئة الصفحة وتحميل الإعدادات المحفوظة
===================================== */
document.addEventListener('DOMContentLoaded', function() {
// استرداد عداد OCR وتاريخ آخر معالجة من localStorage
const savedCount = localStorage.getItem('ocrPagesCount');
const lastDate = localStorage.getItem('lastOcrDate');
if (savedCount) {
ocrPagesCount = parseInt(savedCount);
document.getElementById('ocrCounter').textContent = ocrPagesCount;
}
if (lastDate) {
document.getElementById('lastOcrDate').textContent = lastDate;
}
// تهيئة نافذة شرح الأخطاء
initErrorPopup();
// إضافة أحداث لأزرار عرض 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('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);
});
});
// إضافة حدث للتبديل بين عرض وإخفاء المسودة
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();
});
});
/* =====================================
دالة المعالجة الرئيسية للملفات
===================================== */
function processFile(file, targetType) {
if (!file) return;
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري فحص نوع الملف...';
document.getElementById('progressBar').style.width = '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');
alert('نوع الملف غير مدعوم. يرجى تحميل ملف PDF أو صورة أو Word أو Excel.');
}
}
/* =====================================
معالجة ملف PDF
===================================== */
async function processPDF(file, targetType) {
try {
document.getElementById('statusText').textContent = 'جاري معالجة ملف PDF...';
document.getElementById('progressBar').style.width = '20%';
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument(arrayBuffer);
const pdf = await loadingTask.promise;
documentPages = [];
selectedPages = [];
document.getElementById('progressBar').style.width = '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;
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%';
document.getElementById('pdfPagesCard').classList.remove('hidden');
displayPDFPages();
// بدء معالجة OCR تلقائيًا
document.getElementById('progressBar').style.width = '80%';
await extractText();
} catch (error) {
console.error('خطأ في معالجة ملف PDF:', error);
alert('حدث خطأ أثناء معالجة ملف PDF');
document.getElementById('processingStatus').classList.add('hidden');
}
}
/* =====================================
معالجة ملف صورة
===================================== */
function processImage(file, targetType) {
document.getElementById('statusText').textContent = 'جاري معالجة الصورة...';
document.getElementById('progressBar').style.width = '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%';
document.getElementById('pdfPagesCard').classList.remove('hidden');
displayPDFPages();
// بدء معالجة OCR تلقائيًا
document.getElementById('progressBar').style.width = '80%';
await extractText();
};
img.src = e.target.result;
};
reader.onerror = function() {
console.error('خطأ في قراءة الصورة');
alert('حدث خطأ أثناء قراءة الصورة');
document.getElementById('processingStatus').classList.add('hidden');
};
reader.readAsDataURL(file);
}
/* =====================================
معالجة ملف Word (DOCX)
===================================== */
async function processDocx(file, targetType) {
try {
document.getElementById('statusText').textContent = 'جاري معالجة ملف Word...';
document.getElementById('progressBar').style.width = '30%';
const arrayBuffer = await file.arrayBuffer();
// استخدام مكتبة Mammoth لاستخراج النص من DOCX
mammoth.extractRawText({ arrayBuffer: arrayBuffer })
.then(function(result) {
document.getElementById('progressBar').style.width = '70%';
const extractedText = result.value;
// تحديث شريط التقدم
document.getElementById('progressBar').style.width = '90%';
// عرض النص المستخرج
displayDocxExtractedText(extractedText);
// استخدام النص كمصدر أو هدف
if (targetType === 'source') {
document.getElementById('sourceText').value = extractedText;
alert('تم استخراج النص من ملف Word وإضافته كنص مصدر');
} else if (targetType === 'target') {
document.getElementById('targetText').value = extractedText;
alert('تم استخراج النص من ملف Word وإضافته كنص هدف');
} else if (targetType === 'extra') {
document.getElementById('sourceExtraText').value = extractedText;
alert('تم استخراج النص من ملف Word وإضافته كمصدر إضافي');
}
document.getElementById('processingStatus').classList.add('hidden');
})
.catch(function(error) {
console.error('خطأ في معالجة ملف Word:', error);
alert('حدث خطأ أثناء معالجة ملف Word');
document.getElementById('processingStatus').classList.add('hidden');
});
} catch (error) {
console.error('خطأ في معالجة ملف Word:', error);
alert('حدث خطأ أثناء معالجة ملف Word');
document.getElementById('processingStatus').classList.add('hidden');
}
}
/* =====================================
عرض النص المستخرج من ملف 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</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%';
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%';
if (excelWorkbook.SheetNames.length > 0) {
// عرض قائمة بأسماء الأوراق
renderSheetSelector(excelWorkbook.SheetNames, targetType);
// اختيار الورقة الأولى تلقائيًا
selectExcelSheet(excelWorkbook.SheetNames[0], targetType);
// استخدام النص كمصدر أو هدف تلقائيًا
useExcelContent(targetType);
} else {
alert('لم يتم العثور على أوراق في ملف Excel');
}
document.getElementById('progressBar').style.width = '100%';
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
}, 500);
} catch (error) {
console.error('خطأ في معالجة ملف Excel:', error);
alert('حدث خطأ أثناء معالجة ملف Excel');
document.getElementById('processingStatus').classList.add('hidden');
}
};
reader.onerror = function() {
alert('حدث خطأ أثناء قراءة الملف');
document.getElementById('processingStatus').classList.add('hidden');
};
reader.readAsArrayBuffer(file);
}
/* =====================================
تهيئة نافذة شرح الأخطاء
===================================== */
function initErrorPopup() {
// إضافة حدث لزر الإغلاق
document.querySelector('.error-popup-close').addEventListener('click', function() {
const popup = document.getElementById('errorPopup');
popup.classList.remove('show');
setTimeout(() => {
popup.style.display = 'none';
}, 300);
});
// إغلاق النافذة عند النقر خارجها
window.addEventListener('click', function(event) {
const popup = document.getElementById('errorPopup');
if (event.target === popup) {
popup.classList.remove('show');
setTimeout(() => {
popup.style.display = 'none';
}, 300);
}
});
}
/* =====================================
دالة عرض شرح الخطأ - محسنة لتكون أكثر ودية
===================================== */
function showErrorExplanation(errorType, errorText, explanation) {
const popup = document.getElementById('errorPopup');
const title = document.getElementById('errorPopupTitle');
const content = document.getElementById('errorPopupContent');
// تعيين العنوان حسب نوع الخطأ
let typeIcon = '';
let typeClass = '';
if (errorType === 'number') {
title.textContent = 'خطأ في الأرقام';
typeIcon = '<i class="fas fa-hashtag text-yellow-500 ml-2"></i>';
typeClass = 'bg-yellow-50 border-yellow-400';
} else if (errorType === 'missing') {
title.textContent = 'نص مفقود';
typeIcon = '<i class="fas fa-minus-circle text-blue-500 ml-2"></i>';
typeClass = 'bg-blue-50 border-blue-400';
} else if (errorType === 'meaning') {
title.textContent = 'اختلاف في المعنى';
typeIcon = '<i class="fas fa-exclamation-circle text-red-500 ml-2"></i>';
typeClass = 'bg-red-50 border-red-400';
} else {
title.textContent = 'تفاصيل الخطأ';
typeIcon = '<i class="fas fa-info-circle text-blue-500 ml-2"></i>';
typeClass = 'bg-blue-50 border-blue-400';
}
// إعداد المحتوى
content.innerHTML = `
<div class="mb-4">
<strong class="block mb-2 flex items-center text-gray-700">
${typeIcon} النص المحدد:
</strong>
<div class="p-3 rounded-lg border-r-4 ${typeClass}">${errorText}</div>
</div>
<div>
<strong class="block mb-2 flex items-center text-gray-700">
<i class="fas fa-lightbulb text-yellow-500 ml-2"></i> الشرح:
</strong>
<div class="bg-blue-50 p-3 rounded-lg border border-blue-200">${explanation}</div>
</div>
<div class="mt-4 p-3 bg-gray-50 rounded-lg flex items-center text-sm text-gray-500">
<i class="fas fa-info-circle ml-2"></i>
انقر خارج النافذة أو اضغط على زر الإغلاق للرجوع
</div>
`;
// عرض النافذة مع تأثير التلاشي
popup.style.display = 'block';
setTimeout(() => {
popup.classList.add('show');
}, 10);
}
/* =====================================
عرض صفحات 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');
}
/* =====================================
تحديد/إلغاء تحديد كل الصفحات
===================================== */
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%';
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) * 100}%`;
// استخدام 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 = '100%';
// عرض النصوص المستخرجة
displayExtractedTexts();
// استخدام النص تلقائيًا
if (currentProcessingMode === 'source') {
useOcrText('source');
} else if (currentProcessingMode === 'target') {
useOcrText('target');
} else if (currentProcessingMode === 'extra') {
document.getElementById('sourceExtraText').value = extractedTexts.join('\n\n');
}
} catch (error) {
console.error('خطأ في استخراج النص:', error);
alert('حدث خطأ أثناء استخراج النص: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
}
}
/* =====================================
استخراج النص من صورة (محدث من النموذج الصغير)
===================================== */
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 {
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]}</h4>
<div>${text.substring(0, 100)}${text.length > 100 ? '...' : ''}</div>
`;
resultPreview.appendChild(pagePreview);
});
// عرض النص الكامل
document.getElementById('resultText').textContent = extractedTexts.join('\n\n');
}
/* =====================================
نسخ النص المستخرج
===================================== */
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;
}
alert(`تم استخدام النص المستخرج كنص ${target === 'source' ? 'مصدر' : 'هدف'}`);
}
/* =====================================
وظائف محرر الصور
===================================== */
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 () => {
const sourceText = document.getElementById('sourceText').value;
const targetText = document.getElementById('targetText').value;
// مسح الرسائل السابقة وإظهار النتائج
document.getElementById('errorsList').innerHTML = '';
document.getElementById('resultSection').classList.remove('hidden');
if (!sourceText || !targetText) {
addError('يرجى إدخال كلا النصين المصدر والهدف');
return;
}
try {
addError('جارٍ تقسيم النصوص ومزامنتها...', 'info');
document.getElementById('processingStatus').classList.remove('hidden');
// تقسيم النصوص إلى أجزاء متزامنة بحد أقصى 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');
let totalNumberErrors = 0;
let totalMissingErrors = 0;
let totalMeaningErrors = 0;
// تحليل كل قسم على حدة
for (let i = 0; i < segments.length; i++) {
document.getElementById('progressBar').style.width = `${((i + 1) / segments.length) * 100}%`;
document.getElementById('statusText').textContent = `جاري تحليل القسم ${i+1} من ${segments.length}...`;
// استدعاء دالة تحليل القسم
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));
}
// إعداد الملخص النهائي
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');
}
// تجهيز العرض الكلاسيكي
displayClassicView(sourceText, targetText);
// تجهيز العرض المقسم
displaySegmentedView();
// تجهيز العرض التفاعلي
setupInteractiveView();
} catch (error) {
console.error('Error during analysis:', error);
addError('حدث خطأ أثناء التحليل: ' + error.message, 'error');
} finally {
document.getElementById('processingStatus').classList.add('hidden');
}
});
/* =====================================
دالة إضافة التمييز المبدئي للنص
===================================== */
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%';
// استدعاء نموذج 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%';
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%';
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 = 'تم تقسيم النصوص بنجاح!';
// إخفاء حالة المعالجة بعد فترة قصيرة
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}...`;
// برومبت محسن للتحليل
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>
<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 displayClassicView(sourceText, targetText) {
// دمج نتائج التحليل من جميع الأقسام
let combinedAnalysis = '';
analysisSegments.forEach((segment, index) => {
if (index > 0) combinedAnalysis += '\n\n---\n\n';
combinedAnalysis += segment.analysis;
});
// تطبيق التحديد على النصوص الكاملة مع المقارنة
document.getElementById('sourceTextReview').innerHTML = applyHighlights(sourceText, targetText, combinedAnalysis);
document.getElementById('targetTextReview').innerHTML = applyHighlights(targetText, sourceText, combinedAnalysis);
// إعداد شرح الأخطاء بطريقة محسنة
document.getElementById('classicViewExplanation').innerHTML = generateExplanation(sourceText, targetText, combinedAnalysis);
// إضافة مستمعي الأحداث للنصوص المحددة
setTimeout(() => {
document.querySelectorAll('.sentence-with-error').forEach(element => {
element.addEventListener('click', function() {
// الحصول على النص الكامل للجملة
const sentenceText = this.textContent;
// تحديد نوع الخطأ من خلال الفئات داخل الجملة
let errorType = 'general';
let errorText = sentenceText;
// البحث عن الخطأ المحدد داخل الجملة
const numberHighlight = this.querySelector('.highlight-number');
const missingHighlight = this.querySelector('.highlight-missing');
const meaningHighlight = this.querySelector('.highlight-meaning');
if (numberHighlight) {
errorType = 'number';
errorText = numberHighlight.textContent;
} else if (missingHighlight) {
errorType = 'missing';
errorText = missingHighlight.textContent;
} else if (meaningHighlight) {
errorType = 'meaning';
errorText = meaningHighlight.textContent;
}
// إعداد شرح أكثر ودية
let explanation = '';
if (errorType === 'number') {
explanation = `يا صديقي، لاحظت وجود خطأ في الأرقام في هذه الجملة. <br><br>الرقم "${errorText}" في النص المصدر لا يتطابق مع النص الهدف. يرجى التحقق من صحة الرقم والتأكد من تطابقه في كلا النصين.`;
} else if (errorType === 'missing') {
explanation = `يا صديقي، هناك نص مفقود في الترجمة! <br><br>النص "${errorText}" موجود في المصدر ولكن لا يوجد ما يقابله في النص الهدف. يرجى إضافة هذا النص المفقود للحفاظ على اكتمال المعنى.`;
} else if (errorType === 'meaning') {
explanation = `يا صديقي، يبدو أن هناك اختلاف في المعنى! <br><br>في الجملة "${sentenceText}" النص الذي في المصدر قد تم ترجمته بمعنى مختلف عما هو مقصود. تحتاج إلى مراجعة ترجمة "${errorText}" للتأكد من نقل المعنى الصحيح.`;
} else {
explanation = `يا صديقي، هناك خطأ في هذه الجملة: "${sentenceText}"<br><br>يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`;
}
showErrorExplanation(errorType, sentenceText, explanation);
});
});
}, 100);
}
/* =====================================
دالة تمييز النص - محسنة لتمييز الجمل الكاملة
===================================== */
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 explanation = '';
if (errorType === 'number') {
explanation = `يا صديقي، لاحظت وجود خطأ في الأرقام في الجملة رقم ${sentenceNumber} هذه. <br><br>الرقم "${errorSpecificText}" في النص المصدر لا يتطابق مع النص الهدف. تحتاج لتصحيح الرقم في الترجمة.`;
} else if (errorType === 'missing') {
explanation = `يا صديقي، هناك نص مفقود في الترجمة في الجملة رقم ${sentenceNumber}! <br><br>النص "${errorSpecificText}" موجود في المصدر ولكن لا يوجد ما يقابله في النص الهدف. يرجى إضافة هذا النص المفقود للحفاظ على اكتمال المعنى.`;
} else if (errorType === 'meaning') {
explanation = `يا صديقي، يبدو أن هناك اختلاف في المعنى في الجملة رقم ${sentenceNumber}! <br><br>النص "${errorSpecificText}" في المصدر تمت ترجمته بشكل مختلف عما هو مقصود. تحتاج إلى مراجعة الترجمة للتأكد من دقتها.`;
} else {
explanation = `يا صديقي، هناك خطأ في هذه الجملة رقم ${sentenceNumber}: "${sentenceText}"<br><br>يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`;
}
showErrorExplanation(errorType, sentenceText, explanation);
});
});
}, 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 generateExplanation(sourceText, targetText, analysisOutput) {
// إضافة تحليل محسن للنصوص المفقودة
const numberErrors = [];
const missingErrors = [];
const meaningErrors = [];
// استخراج الأخطاء من تحليل النص
const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g));
numberMatches.forEach(match => {
numberErrors.push({
text: match[1],
explanation: 'يا صديقي، الرقم في النص المصدر يختلف عن الرقم في النص الهدف. يجب التأكد من تطابق الأرقام في كلا النصين.'
});
});
const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g));
missingMatches.forEach(match => {
missingErrors.push({
text: match[1],
explanation: 'يا صديقي، هذا النص موجود في المصدر ولكنه مفقود في الترجمة. يجب إضافته للحفاظ على اكتمال المعنى.'
});
});
const meaningMatches = Array.from(analysisOutput.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g));
meaningMatches.forEach(match => {
meaningErrors.push({
text: match[1],
explanation: 'يا صديقي، هذا النص تم ترجمته بمعنى مختلف عما هو في النص الأصلي. تحتاج إلى مراجعة الترجمة للتأكد من نقل المعنى الصحيح.'
});
});
// البحث عن النصوص المفقودة تماما
const completelyMissing = [];
const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim());
for (const paragraph of sourceParagraphs) {
if (paragraph.trim().length < 10) continue;
// فحص إذا كانت الفقرة مفقودة تماما
if (!targetText.includes(paragraph) && !isPartiallyIncluded(paragraph, targetText)) {
if (paragraph.length > 100) {
completelyMissing.push({
text: paragraph.substring(0, 100) + '...',
explanation: 'يا صديقي، هذا النص مفقود تمامًا في الترجمة. النص الأصلي يحتوي على هذه المعلومات المهمة التي تحتاج إلى إضافتها.'
});
} else {
completelyMissing.push({
text: paragraph,
explanation: 'يا صديقي، هذا النص مفقود تمامًا في الترجمة. النص الأصلي يحتوي على هذه المعلومات المهمة التي تحتاج إلى إضافتها.'
});
}
}
}
// إضافة النصوص المفقودة إلى قائمة الأخطاء
completelyMissing.forEach(missing => {
missingErrors.push(missing);
});
// إنشاء المخرجات المحسنة
if (numberErrors.length === 0 && missingErrors.length === 0 && meaningErrors.length === 0) {
return `
<div class="flex flex-col items-center justify-center p-6 bg-green-50 rounded-xl border border-green-200">
<div class="text-5xl text-green-500 mb-3">
<i class="fas fa-check-circle"></i>
</div>
<p class="text-lg font-bold text-green-700">النصوص متطابقة تماماً ولا توجد فروقات تحتاج للتنبيه.</p>
<p class="text-sm text-green-600 mt-2">تهانينا! الترجمة دقيقة ومكتملة.</p>
</div>`;
}
let html = `
<div class="summary-grid">
<div class="summary-card ${numberErrors.length > 0 ? 'bg-yellow-50' : 'bg-gray-50'}">
<div class="summary-card-header ${numberErrors.length > 0 ? 'text-yellow-600' : 'text-gray-600'}">${numberErrors.length}</div>
<div class="summary-card-title">
<i class="fas fa-hashtag ${numberErrors.length > 0 ? 'text-yellow-500' : 'text-gray-400'}"></i>
اختلافات الأرقام
</div>
</div>
<div class="summary-card ${missingErrors.length > 0 ? 'bg-blue-50' : 'bg-gray-50'}">
<div class="summary-card-header ${missingErrors.length > 0 ? 'text-blue-600' : 'text-gray-600'}">${missingErrors.length}</div>
<div class="summary-card-title">
<i class="fas fa-minus-circle ${missingErrors.length > 0 ? 'text-blue-500' : 'text-gray-400'}"></i>
النصوص المفقودة
</div>
</div>
<div class="summary-card ${meaningErrors.length > 0 ? 'bg-red-50' : 'bg-gray-50'}">
<div class="summary-card-header ${meaningErrors.length > 0 ? 'text-red-600' : 'text-gray-600'}">${meaningErrors.length}</div>
<div class="summary-card-title">
<i class="fas fa-exclamation-circle ${meaningErrors.length > 0 ? 'text-red-500' : 'text-gray-400'}"></i>
اختلافات المعنى
</div>
</div>
</div>
<div class="error-groups">`;
// إضافة مجموعة اختلافات الأرقام
if (numberErrors.length > 0) {
html += `
<div class="error-group">
<div class="error-group-header">
<i class="fas fa-hashtag text-yellow-500"></i>
اختلافات الأرقام (${numberErrors.length})
</div>
<div>`;
// إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل)
const displayedErrors = numberErrors.slice(0, 5);
displayedErrors.forEach((error, index) => {
html += `
<div class="error-item error-number">
<div class="error-item-header">
<span>اختلاف ${index + 1}</span>
</div>
<div class="error-item-body">
${error.text}
</div>
<div class="error-item-footer">
${error.explanation}
</div>
</div>`;
});
// إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء
if (numberErrors.length > 5) {
html += `
<div class="text-center p-2 text-sm text-gray-600">
<i class="fas fa-ellipsis-h ml-1"></i>
و${numberErrors.length - 5} اختلافات أخرى
</div>`;
}
html += `
</div>
</div>`;
}
// إضافة مجموعة النصوص المفقودة
if (missingErrors.length > 0) {
html += `
<div class="error-group">
<div class="error-group-header">
<i class="fas fa-minus-circle text-blue-500"></i>
النصوص المفقودة (${missingErrors.length})
</div>
<div>`;
// إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل)
const displayedErrors = missingErrors.slice(0, 5);
displayedErrors.forEach((error, index) => {
html += `
<div class="error-item error-missing">
<div class="error-item-header">
<span>نص مفقود ${index + 1}</span>
</div>
<div class="error-item-body">
${error.text}
</div>
<div class="error-item-footer">
${error.explanation}
</div>
</div>`;
});
// إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء
if (missingErrors.length > 5) {
html += `
<div class="text-center p-2 text-sm text-gray-600">
<i class="fas fa-ellipsis-h ml-1"></i>
و${missingErrors.length - 5} نصوص مفقودة أخرى
</div>`;
}
html += `
</div>
</div>`;
}
// إضافة مجموعة اختلافات المعنى
if (meaningErrors.length > 0) {
html += `
<div class="error-group">
<div class="error-group-header">
<i class="fas fa-exclamation-circle text-red-500"></i>
اختلافات المعنى (${meaningErrors.length})
</div>
<div>`;
// إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل)
const displayedErrors = meaningErrors.slice(0, 5);
displayedErrors.forEach((error, index) => {
html += `
<div class="error-item error-meaning">
<div class="error-item-header">
<span>اختلاف معنى ${index + 1}</span>
</div>
<div class="error-item-body">
${error.text}
</div>
<div class="error-item-footer">
${error.explanation}
</div>
</div>`;
});
// إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء
if (meaningErrors.length > 5) {
html += `
<div class="text-center p-2 text-sm text-gray-600">
<i class="fas fa-ellipsis-h ml-1"></i>
و${meaningErrors.length - 5} اختلافات معنى أخرى
</div>`;
}
html += `
</div>
</div>`;
}
html += `
</div>
<div class="mt-6 p-4 bg-green-50 rounded-lg border border-green-200">
<h4 class="font-bold text-green-800 mb-2 flex items-center">
<i class="fas fa-lightbulb ml-2"></i>
توصيات للتحسين
</h4>
<ul class="list-disc pr-8 text-sm text-green-700 space-y-1">
${numberErrors.length > 0 ? '<li>يا صديقي، راجع الأرقام في النص وتأكد من تطابقها بين النص المصدر والترجمة.</li>' : ''}
${missingErrors.length > 0 ? '<li>يا صديقي، هناك نصوص مفقودة تحتاج إلى إضافتها في الترجمة للحفاظ على اكتمال المحتوى.</li>' : ''}
${meaningErrors.length > 0 ? '<li>يا صديقي، هناك بعض الاختلافات في المعنى. صحح ترجمة العبارات التي تم تغيير معناها.</li>' : ''}
<li>استخدم وضع العرض المقسم للتعديل الدقيق للنصوص.</li>
</ul>
</div>`;
return html;
}
/* =====================================
توليد شرح للعرض المقسم
===================================== */
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>
</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>