rehan / indexproo.html
joermd's picture
Create indexproo.html
c96afc8 verified
raw
history blame
193 kB
<!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>
/* ================================
تنسيقات الحركات والتأثيرات
================================= */
@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;
}
/* تنسيقات جديدة لعرض المعاينة */
.preview-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.preview-table th, .preview-table td {
padding: 12px 15px;
text-align: right;
border: 1px solid #e5e7eb;
}
.preview-table th {
background-color: #f3f4f6;
font-weight: 600;
}
.preview-table tr:nth-child(even) {
background-color: #f9fafb;
}
.preview-table tr:hover {
background-color: #f0f9ff;
}
/* تنسيقات جديدة للمطابقة التامة */
.perfect-match {
background-color: #d1fae5;
padding: 3px 8px;
border-radius: 4px;
font-weight: 500;
color: #065f46;
display: inline-block;
}
/* تنسيقات جديدة لمسح النص المحدد */
.text-selection-area {
position: relative;
margin-top: 10px;
}
.text-selection-tools {
position: absolute;
top: -40px;
right: 0;
background-color: #374151;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
display: none;
z-index: 10;
animation: fadeInDown 0.3s;
}
@keyframes fadeInDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.text-selection-tools.visible {
display: flex;
}
.selection-tool {
padding: 8px 12px;
color: white;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
border-right: 1px solid #4b5563;
}
.selection-tool:last-child {
border-right: none;
}
.selection-tool:hover {
background-color: #4b5563;
}
.selection-tool i {
margin-left: 6px;
}
/* تنسيقات جديدة لجداول التحليل */
.analysis-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.08);
}
.analysis-table th, .analysis-table td {
padding: 12px 15px;
text-align: right;
}
.analysis-table th {
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
font-weight: 600;
}
.analysis-table tr {
border-bottom: 1px solid #e5e7eb;
}
.analysis-table tr:last-child {
border-bottom: none;
}
.analysis-table tbody tr:nth-child(even) {
background-color: #f8fafc;
}
.analysis-table tbody tr:hover {
background-color: #f0f9ff;
}
/* تنسيقات طباعة التقرير */
@media print {
body {
padding: 0;
margin: 0;
}
header, .logo-container, button, .view-selector, .error-filters,
.explanation-panel, #errorsList, #programExplanation, .view-btn {
display: none !important;
}
.result-section {
break-inside: avoid;
}
.segment-comparison {
break-inside: avoid;
page-break-inside: avoid;
margin: 15px 0;
border: 1px solid #ddd;
box-shadow: none;
}
.text-comparison {
break-inside: avoid;
page-break-inside: avoid;
margin: 10px 0;
box-shadow: none;
border: 1px solid #ddd;
}
.highlight-number, .highlight-missing, .highlight-meaning {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
/* تحسينات لعرض Excel */
.excel-preview {
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: auto;
max-height: 500px;
margin: 15px 0;
}
.excel-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.sheet-selector {
padding: 5px 10px;
background-color: #f3f4f6;
border: 1px solid #d1d5db;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
}
.sheet-selector:hover {
background-color: #e5e7eb;
}
.sheet-selector.active {
background-color: #3b82f6;
color: white;
border-color: #2563eb;
}
/* =============================
تحسينات عرض الأمثلة الثلاثة
============================= */
.example-container {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.example-header {
background-color: #f8fafc;
padding: 12px 16px;
border-bottom: 1px solid #e2e8f0;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.example-header .badge {
font-size: 0.75rem;
font-weight: 500;
padding: 4px 8px;
border-radius: 9999px;
}
.example-content {
padding: 16px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.example-source, .example-target {
padding: 12px;
border-radius: 6px;
line-height: 1.8;
}
.example-source {
background-color: #f0f9ff;
border: 1px solid #bfdbfe;
}
.example-target {
background-color: #fdf2f8;
border: 1px solid #fbcfe8;
}
.example-action {
padding: 12px 16px;
background-color: #f8fafc;
border-top: 1px solid #e2e8f0;
display: flex;
justify-content: flex-end;
}
.example-btn {
padding: 6px 12px;
background-color: #3b82f6;
color: white;
font-weight: 500;
border-radius: 4px;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
}
.example-btn:hover {
background-color: #2563eb;
}
</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>
<!-- ============ شرح البرنامج - مبسط ومحسن ============ -->
<section id="programExplanation" class="max-w-6xl mx-auto px-4 mb-8">
<div class="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
<h3 class="text-xl font-bold mb-3 text-blue-700 flex items-center">
<i class="fas fa-info-circle ml-2"></i> نبذة مختصرة
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-yellow-50 p-4 rounded-lg border border-yellow-200 shadow-sm animate-scale hover:shadow-md transition-all">
<div class="font-bold text-yellow-800 mb-2 flex items-center">
<i class="fas fa-hashtag ml-2 text-yellow-600"></i> تحديد الأرقام
</div>
<p class="text-gray-700">اكتشاف الأرقام المختلفة بين النص المصدر والهدف بجميع أشكالها (عربية، هندية، إنجليزية).</p>
</div>
<div class="bg-blue-50 p-4 rounded-lg border border-blue-200 shadow-sm animate-scale hover:shadow-md transition-all">
<div class="font-bold text-blue-800 mb-2 flex items-center">
<i class="fas fa-minus-circle ml-2 text-blue-600"></i> النصوص المفقودة
</div>
<p class="text-gray-700">تحديد النصوص الموجودة في المصدر والمفقودة في الترجمة، سواء كانت مفقودة كلياً أو جزئياً.</p>
</div>
<div class="bg-red-50 p-4 rounded-lg border border-red-200 shadow-sm animate-scale hover:shadow-md transition-all">
<div class="font-bold text-red-800 mb-2 flex items-center">
<i class="fas fa-exclamation-circle ml-2 text-red-600"></i> اختلافات المعنى
</div>
<p class="text-gray-700">اكتشاف التغييرات في المعنى بين النص الأصلي والترجمة التي تؤثر على المضمون.</p>
</div>
</div>
<div class="mt-4 p-4 bg-blue-50 rounded-lg border border-blue-100">
<h4 class="font-bold text-blue-700 mb-2">طرق العرض المتعددة:</h4>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div class="flex items-center">
<i class="fas fa-columns text-blue-500 ml-2"></i>
<span>العرض الكلاسيكي</span>
</div>
<div class="flex items-center">
<i class="fas fa-th-large text-blue-500 ml-2"></i>
<span>العرض المقسم</span>
</div>
<div class="flex items-center">
<i class="fas fa-exchange-alt text-blue-500 ml-2"></i>
<span>العرض التفاعلي</span>
</div>
</div>
</div>
</div>
</section>
<!-- ============ أمثلة توضيحية محسنة - (الإضافة الجديدة كما طلب المستخدم) ============ -->
<section class="max-w-6xl mx-auto px-4 mb-8">
<div class="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
<h3 class="text-xl font-bold mb-5 text-blue-700 flex items-center">
<i class="fas fa-lightbulb ml-2"></i> أمثلة توضيحية
</h3>
<!-- مثال 1: اختلافات أرقام -->
<div class="example-container">
<div class="example-header">
<div class="flex items-center">
<i class="fas fa-hashtag text-yellow-500 ml-2"></i>
<span>مثال على اختلافات الأرقام</span>
</div>
<span class="badge bg-yellow-100 text-yellow-800">أرقام</span>
</div>
<div class="example-content">
<div class="example-source">
في عام <span class="highlight-number">٢٠٢٣</span>، تم تنفيذ المشروع بتكلفة <span class="highlight-number">٥٠٠٠٠٠</span> ريال، وشارك فيه <span class="highlight-number">٢٥</span> موظفًا على مدار <span class="highlight-number">١٢</span> شهرًا. وتم توفير <span class="highlight-number">٣٠٪</span> من الميزانية المخصصة للمشروع.
</div>
<div class="example-target">
In <span class="highlight-number">2022</span>, the project was implemented at a cost of <span class="highlight-number">450,000</span> riyals, with <span class="highlight-number">23</span> employees participating over <span class="highlight-number">10</span> months. <span class="highlight-number">35%</span> of the project's allocated budget was saved.
</div>
</div>
<div class="example-action">
<button class="example-btn" onclick="useExample(1)">استخدام هذا المثال</button>
</div>
</div>
<!-- مثال 2: نص مفقود -->
<div class="example-container">
<div class="example-header">
<div class="flex items-center">
<i class="fas fa-minus-circle text-blue-500 ml-2"></i>
<span>مثال على النص المفقود</span>
</div>
<span class="badge bg-blue-100 text-blue-800">نص مفقود</span>
</div>
<div class="example-content">
<div class="example-source">
يعتبر التعليم المستمر من أهم أدوات النجاح في عصرنا الحالي. ويشمل ذلك التعلم الذاتي والدورات التدريبية <span class="highlight-missing">والقراءة المستمرة</span>. كما أن <span class="highlight-missing">التطبيق العملي للمعرفة المكتسبة يساعد على ترسيخها وتطويرها</span>.
</div>
<div class="example-target">
Continuous education is considered one of the most important tools for success in our current era. This includes self-learning and training courses. Practical application of acquired knowledge helps to consolidate and develop it.
</div>
</div>
<div class="example-action">
<button class="example-btn" onclick="useExample(2)">استخدام هذا المثال</button>
</div>
</div>
<!-- مثال 3: اختلاف معنى -->
<div class="example-container">
<div class="example-header">
<div class="flex items-center">
<i class="fas fa-exclamation-circle text-red-500 ml-2"></i>
<span>مثال على اختلاف المعنى</span>
</div>
<span class="badge bg-red-100 text-red-800">اختلاف معنى</span>
</div>
<div class="example-content">
<div class="example-source">
تتميز هذه السيارة بأنظمة <span class="highlight-meaning">أمان</span> متطورة تشمل نظام منع انغلاق المكابح ونظام التحكم الإلكتروني بالثبات. وتعمل بمحرك <span class="highlight-meaning">اقتصادي يوفر في استهلاك الوقود</span> بنسبة عالية.
</div>
<div class="example-target">
This car features advanced <span class="highlight-meaning">entertainment</span> systems including an anti-lock braking system and electronic stability control. It runs on a <span class="highlight-meaning">high-performance engine that provides exceptional speed and acceleration</span>.
</div>
</div>
<div class="example-action">
<button class="example-btn" onclick="useExample(3)">استخدام هذا المثال</button>
</div>
</div>
</div>
</section>
<!-- ============ المحتوى الرئيسي ============ -->
<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 class="mt-2">
<button id="processSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
معالجة OCR للسورس
</button>
</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 class="mt-2">
<button id="processTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
معالجة OCR للتارجت
</button>
</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> النص المصدر
<span class="mr-2 text-sm font-normal text-gray-500">(اكتب أو استخدم أحد الأمثلة أعلاه)</span>
</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> النص الهدف
<span class="mr-2 text-sm font-normal text-gray-500">(اكتب أو استخدم أحد الأمثلة أعلاه)</span>
</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="explanation-panel mt-4">
<h5>شرح الأخطاء</h5>
<div id="classicViewExplanation"></div>
</div>
</div>
<!-- 2. طريقة العرض المقسمة - محسنة -->
<div id="segmentView" class="view-container">
<div id="segmentedComparisonContainer" class="space-y-4">
<!-- سيتم إنشاء المقاطع هنا بواسطة JavaScript -->
</div>
<!-- قسم شرح الأخطاء للعرض المقسم -->
<div class="explanation-panel mt-4">
<h5>شرح الأخطاء في المقاطع</h5>
<div id="segmentViewExplanation"></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="explanation-panel mt-4">
<h5>التفاصيل والتوضيحات</h5>
<div id="interactiveViewExplanation"></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>
</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 DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions';
const DEEPSEEK_API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01';
// إعدادات API ChatGPT-4o
const CHATGPT_API_URL = 'https://api.openai.com/v1/chat/completions';
const CHATGPT_API_KEY = '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';
// إعدادات API4AI OCR
const RAPIDAPI_KEY = 'eb11693cddmshb8bd157e05b74acp1f6aa4jsn4369fa546e55';
const OCR_API_URL = 'https://ocr43.p.rapidapi.com/v1/results';
// متغيرات عامة للتحليل
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;
/* =====================================
أمثلة للاختبار - الميزة الجديدة
===================================== */
const examples = [
// مثال 1: الأرقام
{
source: "في عام ٢٠٢٣، تم تنفيذ المشروع بتكلفة ٥٠٠٠٠٠ ريال، وشارك فيه ٢٥ موظفًا على مدار ١٢ شهرًا. وتم توفير ٣٠٪ من الميزانية المخصصة للمشروع.",
target: "In 2022, the project was implemented at a cost of 450,000 riyals, with 23 employees participating over 10 months. 35% of the project's allocated budget was saved."
},
// مثال 2: النصوص المفقودة
{
source: "يعتبر التعليم المستمر من أهم أدوات النجاح في عصرنا الحالي. ويشمل ذلك التعلم الذاتي والدورات التدريبية والقراءة المستمرة. كما أن التطبيق العملي للمعرفة المكتسبة يساعد على ترسيخها وتطويرها.",
target: "Continuous education is considered one of the most important tools for success in our current era. This includes self-learning and training courses. Practical application of acquired knowledge helps to consolidate and develop it."
},
// مثال 3: اختلافات المعنى
{
source: "تتميز هذه السيارة بأنظمة أمان متطورة تشمل نظام منع انغلاق المكابح ونظام التحكم الإلكتروني بالثبات. وتعمل بمحرك اقتصادي يوفر في استهلاك الوقود بنسبة عالية.",
target: "This car features advanced entertainment systems including an anti-lock braking system and electronic stability control. It runs on a high-performance engine that provides exceptional speed and acceleration."
}
];
// دالة استخدام الأمثلة
function useExample(exampleIndex) {
if (exampleIndex >= 1 && exampleIndex <= examples.length) {
const example = examples[exampleIndex - 1];
document.getElementById('sourceText').value = example.source;
document.getElementById('targetText').value = example.target;
// تنفيذ التحليل تلقائيًا
setTimeout(() => {
document.getElementById('submitBtn').click();
}, 500);
}
}
/* =====================================
تهيئة الصفحة وتحميل الإعدادات المحفوظة
===================================== */
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();
// إضافة أحداث النقر لأزرار معالجة OCR
document.getElementById('processSourceBtn').addEventListener('click', function() {
processFileForOCR('source');
});
document.getElementById('processTargetBtn').addEventListener('click', function() {
processFileForOCR('target');
});
// إضافة أحداث لأزرار عرض 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);
// إضافة أحداث لمعالجة ملفات Excel
document.getElementById('sourceFile').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file && (file.name.endsWith('.xlsx') || file.name.endsWith('.xls'))) {
processExcelFile(file, 'source');
}
});
document.getElementById('targetFile').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file && (file.name.endsWith('.xlsx') || file.name.endsWith('.xls'))) {
processExcelFile(file, 'target');
}
});
document.getElementById('sourceExtraFile').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file && (file.name.endsWith('.xlsx') || file.name.endsWith('.xls'))) {
processExcelFile(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.querySelectorAll('.example-btn').forEach(btn => {
btn.addEventListener('click', function() {
const exampleId = parseInt(this.getAttribute('data-example'));
useExample(exampleId);
});
});
});
/* =====================================
معالجة ملفات Excel
===================================== */
function processExcelFile(file, targetType) {
document.getElementById('processingStatus').classList.remove('hidden');
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);
} 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 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 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);
}
/* =====================================
دالة توحيد الأرقام بمختلف أشكالها
===================================== */
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());
}
/* =====================================
معالجة ملفات (DOCX، PDF)
===================================== */
async function processFile(file) {
let text = "";
if (file.type === 'application/pdf') {
try {
// تحميل PDF واستخراج النص
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument(arrayBuffer);
const pdf = await loadingTask.promise;
const numPages = pdf.numPages;
let fullText = "";
for (let i = 1; i <= numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
const pageText = content.items.map(item => item.str).join(' ');
fullText += pageText + "\n\n";
}
text = fullText;
} catch (error) {
console.error('خطأ في استخراج النص من PDF:', error);
throw new Error('فشل استخراج النص من ملف PDF');
}
} else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
try {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
text = result.value;
} catch (error) {
console.error('خطأ في استخراج النص من DOCX:', error);
throw new Error('فشل استخراج النص من ملف Word');
}
} else if (file.type === 'application/vnd.ms-excel' ||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
try {
const arrayBuffer = await file.arrayBuffer();
const workbook = XLSX.read(arrayBuffer, {type: 'array'});
// استخراج النص من الورقة الأولى
const firstSheet = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheet];
const data = XLSX.utils.sheet_to_json(worksheet, {header: 1});
// تحويل البيانات إلى نص
text = data.map(row => row.join('\t')).join('\n');
} catch (error) {
console.error('خطأ في استخراج النص من Excel:', error);
throw new Error('فشل استخراج النص من ملف Excel');
}
} else {
throw new Error('نوع الملف غير مدعوم');
}
return text;
}
/* =====================================
دوال رفع الملفات وإدخال النصوص
===================================== */
// رفع ملف السورس
document.getElementById('sourceFile')?.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
// إذا كان الملف PDF أو صورة أو Excel، لا نقوم باستخراج النص تلقائيًا
if (file.type === 'application/pdf' || file.type.startsWith('image/') ||
file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) {
return;
}
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري معالجة ملف السورس...';
document.getElementById('progressBar').style.width = '30%';
try {
const text = await processFile(file);
document.getElementById('progressBar').style.width = '90%';
document.getElementById('sourceText').value = text;
document.getElementById('statusText').textContent = 'تم استخراج النص بنجاح!';
document.getElementById('progressBar').style.width = '100%';
// إظهار رسالة نجاح
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
}, 800);
} catch (error) {
console.error('Error processing source file:', error);
addError('خطأ في معالجة ملف السورس: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
}
});
// رفع ملف التارجت
document.getElementById('targetFile')?.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
// إذا كان الملف PDF أو صورة أو Excel، لا نقوم باستخراج النص تلقائيًا
if (file.type === 'application/pdf' || file.type.startsWith('image/') ||
file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) {
return;
}
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري معالجة ملف التارجت...';
document.getElementById('progressBar').style.width = '30%';
try {
const text = await processFile(file);
document.getElementById('progressBar').style.width = '90%';
document.getElementById('targetText').value = text;
document.getElementById('statusText').textContent = 'تم استخراج النص بنجاح!';
document.getElementById('progressBar').style.width = '100%';
// إظهار رسالة نجاح
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
}, 800);
} catch (error) {
console.error('Error processing target file:', error);
addError('خطأ في معالجة ملف التارجت: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
}
});
// رفع ملف المصادر الإضافية
document.getElementById('sourceExtraFile')?.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
// إذا كان الملف Excel، لا نقوم باستخراج النص تلقائيًا
if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) {
return;
}
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري معالجة ملف المصدر الإضافي...';
document.getElementById('progressBar').style.width = '30%';
try {
const text = await processFile(file);
document.getElementById('progressBar').style.width = '90%';
document.getElementById('sourceExtraText').value = text;
document.getElementById('statusText').textContent = 'تم استخراج النص بنجاح!';
document.getElementById('progressBar').style.width = '100%';
// إظهار رسالة نجاح
setTimeout(() => {
document.getElementById('processingStatus').classList.add('hidden');
}, 800);
} catch (error) {
console.error('Error processing extra source file:', error);
addError('خطأ في معالجة ملف المصدر الإضافي: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
}
});
/* =====================================
دالة عرض الأخطاء والرسائل - محسنة
===================================== */
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 highlightSentenceErrors(text, errors) {
if (!errors || errors.length === 0) return text;
// نسخ النص لتجنب تغيير النص الأصلي
let highlightedText = text;
// فرز الأخطاء حسب طول النص المحدد (من الأطول للأقصر) لتجنب تداخل التحديدات
const sortedErrors = [...errors].sort((a, b) =>
b.errorText.length - a.errorText.length
);
// تطبيق التحديد لكل خطأ
for (const error of sortedErrors) {
// تحديد نوع الخطأ وتطبيق التنسيق المناسب
let highlightClass = '';
let icon = '';
if (error.type === 'number') {
highlightClass = 'highlight-number';
icon = 'hashtag';
} else if (error.type === 'missing') {
highlightClass = 'highlight-missing';
icon = 'minus-circle';
} else if (error.type === 'meaning') {
highlightClass = 'highlight-meaning';
icon = 'exclamation-circle';
}
// تنفيذ الاستبدال مع الاحتفاظ بمعرف فريد للخطأ
const errorId = `error-${Math.random().toString(36).substring(2, 9)}`;
const explanation = encodeURIComponent(error.explanation || 'لا يوجد شرح متاح');
const reference = encodeURIComponent(error.reference || 'لا يوجد مرجع متاح');
// إضافة أيقونة صغيرة داخل التمييز
const replacement = `<span class="${highlightClass}" data-error-id="${errorId}" data-error-type="${error.type}" data-explanation="${explanation}" data-reference="${reference}" title="انقر للتفاصيل">${error.errorText}</span>`;
// البحث عن الجملة الكاملة وتحديدها
const sentenceIndex = highlightedText.indexOf(error.sentence);
if (sentenceIndex !== -1) {
// تحديد الجملة بكاملها بفئة خاصة
const beforeSentence = highlightedText.substring(0, sentenceIndex);
const afterSentence = highlightedText.substring(sentenceIndex + error.sentence.length);
// استبدال النص المحدد داخل الجملة
let updatedSentence = error.sentence;
const errorTextIndex = updatedSentence.indexOf(error.errorText);
if (errorTextIndex !== -1) {
updatedSentence =
updatedSentence.substring(0, errorTextIndex) +
replacement +
updatedSentence.substring(errorTextIndex + error.errorText.length);
}
// وضع الجملة المحدثة في سياقها مع تمييز الجملة بأكملها
highlightedText =
beforeSentence +
`<span class="sentence-with-error" data-error-id="${errorId}">${updatedSentence}</span>` +
afterSentence;
} else {
// إذا تعذر العثور على الجملة الكاملة، قم بتحديد النص المحدد فقط
highlightedText = highlightedText.replace(
new RegExp(escapeRegExp(error.errorText), 'g'),
replacement
);
}
}
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 countWords(text) {
return text.trim().split(/\s+/).filter(word => word !== "").length;
}
// هروب أحرف Regex الخاصة
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// تقسيم النص إلى أسطر مع عرض رقم السطر
function splitIntoLines(text) {
return text.split('\n').map((line, i) => {
// تحسين شكل عرض الأسطر
return `
<div class="line-item">
<span class="line-number">${i+1}</span>
<span class="line-text">${line || '&nbsp;'}</span>
</div>`;
}).join('');
}
// الحصول على رقم السطر لظهور عبارة معينة
function getLineNumber(text, substring) {
const index = text.indexOf(substring);
if (index === -1) return "غير محدد";
return text.substring(0, index).split("\n").length;
}
// اقتطاع النص إذا كان طويلا جدا
function truncateText(text, maxLength) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
// تنسيق نص التحليل بتمييز الكلمات المهمة
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 highlightSyncWord(text, syncWord) {
if (!syncWord || !text.includes(syncWord)) return text;
// تجنب تمييز الكلمة إذا كانت جزءًا من كلمة أكبر
const regex = new RegExp(`(\\b${escapeRegExp(syncWord)}\\b)`, 'g');
// جعل الظهور الأخير للكلمة ممَيز
const lastIndex = text.lastIndexOf(syncWord);
if (lastIndex !== -1) {
const beforeSync = text.substring(0, lastIndex);
const afterSync = text.substring(lastIndex + syncWord.length);
return beforeSync + '<span class="sync-word">' + syncWord + '</span>' + afterSync;
}
return text;
}
/* =====================================
دوال التمييز (Highlighting) للنتائج
دالة محسنة تدمج آلية الكشف عن النص المفقود
===================================== */
function applyHighlights(originalText, targetText, analysisOutput) {
// تطبيق تمييز للنص المفقود تماما والمفقود جزئيا
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 sentences = splitIntoSentences(enhancedSourceText);
// معالجة كل جملة على حدة وتطبيق التحديد إذا وُجدت مشكلة
const highlightedSentences = sentences.map(sentence => {
let hasError = false;
let highlightedSentence = sentence;
// تحديد اختلافات الأرقام مع مراعاة أشكال الأرقام المختلفة
numberMatches.forEach(phrase => {
if (sentence.includes(phrase)) {
// استخدام المقارنة بعد توحيد الأرقام
const normalizedPhrase = normalizeNumbers(phrase);
const normalizedSentence = normalizeNumbers(sentence);
// تجنب تحديد النص إذا كان الاختلاف فقط في شكل الأرقام
if (normalizedSentence.includes(normalizedPhrase)) {
// لا تعامله كاختلاف جوهري
} else {
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;
}
}
});
// تحديد النصوص المفقودة
missingMatches.forEach(phrase => {
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;
}
});
// تحديد اختلافات المعنى
meaningMatches.forEach(phrase => {
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;
}
});
// إذا كان هناك خطأ، قم بتمييز الجملة كاملة
if (hasError) {
return `<span class="sentence-with-error">${highlightedSentence}</span>`;
}
return highlightedSentence;
});
return highlightedSentences.join(' ');
}
/* =====================================
تطبيق فلتر الأخطاء على جميع طرق العرض
===================================== */
function applyErrorFilter(filterType) {
// تطبيق الفلتر على العرض الكلاسيكي
applyClassicViewFilter(filterType);
// تطبيق الفلتر على العرض المقسم
applySegmentedViewFilter(filterType);
// تطبيق الفلتر على العرض التفاعلي
applyInteractiveViewFilter(filterType);
}
// تطبيق الفلتر على العرض الكلاسيكي
function applyClassicViewFilter(filterType) {
// إذا كان الفلتر "all"، نعرض كل الأخطاء
if (filterType === 'all') {
document.querySelectorAll('#sourceTextReview .highlight-number, #sourceTextReview .highlight-missing, #sourceTextReview .highlight-meaning, #targetTextReview .highlight-number, #targetTextReview .highlight-missing, #targetTextReview .highlight-meaning, #sourceTextReview .completely-missing, #sourceTextReview .partially-missing').forEach(el => {
el.style.display = '';
});
// إعادة تفعيل جميع الجمل
document.querySelectorAll('.sentence-with-error').forEach(el => {
el.classList.remove('opacity-50');
});
return;
}
// إخفاء كل الأخطاء أولاً
document.querySelectorAll('#sourceTextReview .highlight-number, #sourceTextReview .highlight-missing, #sourceTextReview .highlight-meaning, #targetTextReview .highlight-number, #targetTextReview .highlight-missing, #targetTextReview .highlight-meaning, #sourceTextReview .completely-missing, #sourceTextReview .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(`#sourceTextReview ${selector}, #targetTextReview ${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');
});
}
});
}
// تطبيق الفلتر على العرض المقسم
function applySegmentedViewFilter(filterType) {
// استرجاع جميع المقاطع
const segments = document.querySelectorAll('.segment-comparison');
if (filterType === 'all') {
// إظهار جميع المقاطع
segments.forEach(segment => {
segment.style.display = '';
// إظهار جميع الأخطاء
segment.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => {
el.style.display = '';
});
// إعادة تفعيل جميع الجمل
segment.querySelectorAll('.sentence-with-error').forEach(el => {
el.classList.remove('opacity-50');
});
});
return;
}
// إخفاء كل الأخطاء أولاً في جميع المقاطع
segments.forEach(segment => {
segment.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => {
el.style.display = 'none';
});
// إضفاء شفافية على جميع الجمل
segment.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';
}
// إظهار الأخطاء المطلوبة فقط
const highlightsToShow = segment.querySelectorAll(selector);
highlightsToShow.forEach(el => {
el.style.display = '';
// إزالة الشفافية عن الجمل التي تحتوي على هذا النوع من الأخطاء
const errorId = el.getAttribute('data-error-id');
if (errorId) {
segment.querySelectorAll(`.sentence-with-error[data-error-id="${errorId}"]`).forEach(sentence => {
sentence.classList.remove('opacity-50');
});
}
});
// إذا لم يكن هناك أخطاء من النوع المطلوب في هذا المقطع، نخفيه
if (highlightsToShow.length === 0) {
segment.style.display = 'none';
} else {
segment.style.display = '';
}
});
}
// تطبيق الفلتر على العرض التفاعلي
function applyInteractiveViewFilter(filterType) {
// فلترة الفروقات
if (filterType === 'all') {
// إعادة جميع الفروقات
currentDiffIndex = 0;
displayCurrentDifference();
return;
}
// فلترة الفروقات حسب النوع
const filteredDiffs = allDifferences.filter(diff => diff.type === filterType);
// إذا لم نجد فروقات، نعرض رسالة
if (filteredDiffs.length === 0) {
document.getElementById('currentDiffDisplay').innerHTML = `
<div class="flex flex-col items-center justify-center py-8">
<div class="text-5xl text-gray-300 mb-4">
<i class="fas fa-${filterType === 'number' ? 'hashtag' : filterType === 'missing' ? 'minus-circle' : 'exclamation-circle'}"></i>
</div>
<p class="text-gray-500 text-center">لا توجد اختلافات من نوع ${filterType === 'number' ? 'الأرقام' : filterType === 'missing' ? 'النصوص المفقودة' : 'المعنى'}</p>
</div>`;
document.getElementById('diffCounter').textContent = "0/0";
// إخفاء عرض المصدر والهدف
document.getElementById('diffDetailedView').classList.add('hidden');
document.getElementById('diffReference').classList.add('hidden');
// تعطيل أزرار التنقل
document.getElementById('prevDiff').disabled = true;
document.getElementById('nextDiff').disabled = true;
} else {
// تعيين المؤشر للفرق الأول من النوع المطلوب
currentDiffIndex = allDifferences.findIndex(diff => diff.type === filterType);
// عرض الفرق الحالي
displayCurrentDifference();
// تفعيل أزرار التنقل
document.getElementById('prevDiff').disabled = false;
document.getElementById('nextDiff').disabled = false;
}
}
/* =====================================
دالة تقسيم النصوص باستخدام DeepSeek
===================================== */
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. حتى لو كان هناك نص مفقود في أحد النصين، ابحث عن أقرب مقابل له
2. قسّم النصوص إلى أجزاء متزامنة لا تزيد عن 500 كلمة لكل جزء
3. حاول مطابقة العناوين والأرقام والمصطلحات المشتركة بين النصين
أرجع النتائج بصيغة JSON:
{
"segments": [
{
"source": "نص المصدر للفقرة الأولى",
"target": "نص الهدف المقابل للفقرة الأولى"
},
{
"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('فشل استدعاء نموذج لتقسيم النصوص: ' + response.statusText);
}
document.getElementById('progressBar').style.width = '80%';
const data = await response.json();
const result = data.choices[0].message.content.trim();
// استخراج 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);
}
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);
// إعادة تقسيم بسيط كحل احتياطي
return createFallbackSegments(sourceText, targetText);
}
}
// إنشاء تقسيم بسيط كحل احتياطي مع تحسينات
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());
const segments = [];
const maxLength = 500; // الحد الأقصى للكلمات في كل قسم
// تحسين: محاولة مطابقة الفقرات بناءً على المحتوى المشترك
let sourceIndex = 0;
let targetIndex = 0;
while (sourceIndex < sourceParagraphs.length && targetIndex < targetParagraphs.length) {
let currentSource = '';
let currentTarget = '';
let currentWordCount = 0;
const initialSourceIndex = sourceIndex;
const initialTargetIndex = targetIndex;
// إضافة فقرات حتى نصل إلى الحد الأقصى
while (sourceIndex < sourceParagraphs.length &&
targetIndex < targetParagraphs.length &&
currentWordCount < maxLength) {
const sourcePara = sourceParagraphs[sourceIndex];
const targetPara = targetParagraphs[targetIndex];
const sourceWords = sourcePara.split(/\s+/).length;
const targetWords = targetPara.split(/\s+/).length;
// إذا كانت إضافة هذه الفقرة ستتجاوز الحد الأقصى، نتوقف
if (currentWordCount + Math.max(sourceWords, targetWords) > maxLength && currentWordCount > 0) {
break;
}
// إضافة الفقرات الحالية
currentSource += (currentSource ? '\n\n' : '') + sourcePara;
currentTarget += (currentTarget ? '\n\n' : '') + targetPara;
currentWordCount += Math.max(sourceWords, targetWords);
sourceIndex++;
targetIndex++;
}
// إذا لم نتمكن من إضافة أي فقرات، نتوقف لتجنب حلقة لانهائية
if (sourceIndex === initialSourceIndex && targetIndex === initialTargetIndex) {
break;
}
// إضافة القسم الحالي
if (currentSource || currentTarget) {
segments.push({ source: currentSource, target: currentTarget });
}
}
// إضافة الفقرات المتبقية في المصدر
if (sourceIndex < sourceParagraphs.length) {
const remainingSource = sourceParagraphs.slice(sourceIndex).join('\n\n');
segments.push({ source: remainingSource, target: 'نص مفقود في الترجمة' });
}
// إضافة الفقرات المتبقية في الهدف
if (targetIndex < targetParagraphs.length) {
const remainingTarget = targetParagraphs.slice(targetIndex).join('\n\n');
segments.push({ source: 'نص إضافي في الترجمة', target: remainingTarget });
}
return segments;
}
/* =====================================
دالة تحليل قسم واحد باستخدام DeepSeek Chat
===================================== */
async function analyzeAlignedPair(sourceText, targetText, pairNumber) {
try {
// إظهار حالة التحليل
document.getElementById('statusText').textContent = `جاري تحليل القسم ${pairNumber}...`;
// برومبت محسن للتحليل - التركيز على اكتشاف النص المفقود تماما أو جزئيا
const prompt = `قارن النص المصدر والنص الهدف التاليين، وحدد بدقة:
1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي < >
2. النصوص المفقودة: ضع النص المفقود في الترجمة بين علامتي __ __
• تأكد من تحديد النص المفقود تماما بشكل منفصل
• إذا كان جزء من النص مفقود، حدده بدقة
3. اختلافات المعنى: ضع النص الذي تغير معناه بين علامتي [MEANING] [/MEANING]
اعتبر الأرقام بمختلف أنظمتها (العربية والهندية والإنجليزية) متطابقة إذا كانت تمثل نفس الرقم.
قم بتحليل كل جملة على حدة وتحديد الاختلافات بدقة.
لكل اختلاف، قدم شرحاً موجزاً للخطأ والتصحيح المقترح.
انتبه بشكل خاص للنصوص المفقودة تماما في الترجمة وحددها.
النص المصدر:
${sourceText}
النص الهدف:
${targetText}`;
const payload = {
model: "deepseek-reasoner",
messages: [
{ role: "system", content: "أنت خبير لغوي في تحليل ومقارنة النصوص المترجمة بدقة عالية." },
{ role: "user", content: prompt }
],
temperature: 0.2,
max_tokens: 2048
};
// استدعاء DeepSeek 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('فشل استدعاء للتحليل: ' + 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 contentDiv = document.createElement('div');
contentDiv.className = 'section-content';
// استخدام تنسيق محسن للفقرات المتوازية
contentDiv.innerHTML = `
<div class="aligned-paragraphs mb-4">
<div class="paragraph-ar">${segment.source}</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>
`;
}
}
/* =====================================
دالة معالجة عملية التحليل عند الضغط على الزر
===================================== */
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;
}
// مقارنة عدد الكلمات وتنبيه إن وجد اختلاف
const sourceWordCount = countWords(sourceText);
const targetWordCount = countWords(targetText);
if (Math.abs(sourceWordCount - targetWordCount) > sourceWordCount * 0.2) {
addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف بشكل كبير عن النص الهدف (${targetWordCount})`, 'warning');
}
try {
addError('جارٍ تقسيم النصوص باستخدام المقسم...', 'info');
document.getElementById('processingStatus').classList.remove('hidden');
// تقسيم النصوص إلى أجزاء متزامنة باستخدام DeepSeek
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 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('.highlight-number, .highlight-meaning, .highlight-missing, .completely-missing, .partially-missing').forEach(element => {
element.addEventListener('click', function() {
const errorType = this.getAttribute('data-error-type') ||
(this.classList.contains('completely-missing') ? 'missing' :
this.classList.contains('partially-missing') ? 'missing' : 'number');
const errorText = this.textContent;
let explanation = '';
if (this.classList.contains('completely-missing')) {
explanation = 'هذا النص مفقود تماما في الترجمة. يجب إضافته للحفاظ على اكتمال المحتوى.';
} else if (this.classList.contains('partially-missing')) {
explanation = 'هذا النص موجود بشكل جزئي في الترجمة. تأكد من ترجمة كل المحتوى.';
} else {
explanation = decodeURIComponent(this.getAttribute('data-explanation') || 'لا يوجد شرح متاح');
}
showErrorExplanation(errorType, errorText, explanation);
});
});
}, 100);
}
/* =====================================
عرض النتائج في العرض المقسم - محسن
===================================== */
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);
});
// إضافة مستمعي الأحداث للتحديدات
setTimeout(() => {
document.querySelectorAll('.segment-source .highlight-number, .segment-source .highlight-meaning, .segment-target .highlight-missing, .segment-source .completely-missing, .segment-source .partially-missing').forEach(element => {
element.addEventListener('click', function() {
const errorType = this.getAttribute('data-error-type') ||
(this.classList.contains('completely-missing') ? 'missing' :
this.classList.contains('partially-missing') ? 'missing' : 'number');
const errorText = this.textContent;
let explanation = '';
if (this.classList.contains('completely-missing')) {
explanation = 'هذا النص مفقود تماما في الترجمة. يجب إضافته للحفاظ على اكتمال المحتوى.';
} else if (this.classList.contains('partially-missing')) {
explanation = 'هذا النص موجود بشكل جزئي في الترجمة. تأكد من ترجمة كل المحتوى.';
} else {
explanation = decodeURIComponent(this.getAttribute('data-explanation') || 'لا يوجد شرح متاح');
}
showErrorExplanation(errorType, errorText, 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 = `
<p class="mb-2">استخدم الأزرار "التالي" و"السابق" للتنقل بين الاختلافات المختلفة التي تم العثور عليها.</p>
<p>يمكنك تصفية الاختلافات حسب نوعها باستخدام خيارات التصفية الموجودة أعلاه.</p>
`;
}
// الحصول على السياق المحيط بنص معين - محسن
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 showNextDifference() {
if (allDifferences.length === 0) return;
currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length;
displayCurrentDifference();
}
// عرض الاختلاف السابق
function showPreviousDifference() {
if (allDifferences.length === 0) return;
currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % 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 '';
}
/* =====================================
دالة توليد الشرح التفصيلي Organized Explanation
يتم تقسيم الشرح إلى خطوات منظمة
===================================== */
function generateExplanation(sourceText, targetText, analysisOutput) {
// إضافة تحليل محسن للنصوص المفقودة تماما والمفقودة جزئيا
let steps = [];
// البحث عن النصوص المفقودة تماما
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)) {
completelyMissing.push({
text: truncateText(paragraph, 100),
lineNum: getLineNumber(sourceText, paragraph)
});
}
}
// إضافة الخطوة 1: النصوص المفقودة تماما
if (completelyMissing.length > 0) {
completelyMissing.forEach(missing => {
steps.push(`
<li class="mb-3 pb-3 border-b border-gray-200">
<div class="font-bold text-blue-700 mb-2 flex items-center">
<i class="fas fa-minus-circle text-blue-500 ml-2"></i>
<span>النص المفقود تماما:</span>
<span class="mr-2 bg-blue-100 text-blue-800 text-sm px-2 py-1 rounded">السطر ${missing.lineNum}</span>
</div>
<div class="bg-blue-50 p-3 rounded-lg border-r-4 border-blue-400">
${missing.text}
</div>
<div class="mt-2 text-sm text-gray-600">
<i class="fas fa-info-circle ml-1"></i>
هذا النص موجود في المصدر ومفقود تماما في الترجمة.
</div>
</li>`);
});
}
// الخطوة 2: اختلافات الأرقام
const numberRegex = /<([^<>]+)>/g;
let match;
while ((match = numberRegex.exec(analysisOutput)) !== null) {
const phrase = match[1].trim();
if (phrase) {
const lineNum = getLineNumber(sourceText, phrase);
steps.push(`
<li class="mb-3 pb-3 border-b border-gray-200">
<div class="font-bold text-yellow-700 mb-2 flex items-center">
<i class="fas fa-hashtag text-yellow-500 ml-2"></i>
<span>اختلاف رقمي:</span>
<span class="mr-2 bg-yellow-100 text-yellow-800 text-sm px-2 py-1 rounded">السطر ${lineNum}</span>
</div>
<div class="bg-yellow-50 p-3 rounded-lg border-r-4 border-yellow-400">
${phrase}
</div>
<div class="mt-2 text-sm text-gray-600">
<i class="fas fa-info-circle ml-1"></i>
تأكد من تطابق هذا الرقم في النص الهدف.
</div>
</li>`);
}
}
// الخطوة 3: النصوص المفقودة جزئيا
const missingRegex = /__(.*?)__/g;
while ((match = missingRegex.exec(analysisOutput)) !== null) {
const phrase = match[1].trim();
if (phrase) {
const lineNum = getLineNumber(sourceText, phrase);
steps.push(`
<li class="mb-3 pb-3 border-b border-gray-200">
<div class="font-bold text-blue-700 mb-2 flex items-center">
<i class="fas fa-minus-circle text-blue-500 ml-2"></i>
<span>نص مفقود:</span>
<span class="mr-2 bg-blue-100 text-blue-800 text-sm px-2 py-1 rounded">السطر ${lineNum}</span>
</div>
<div class="bg-blue-50 p-3 rounded-lg border-r-4 border-blue-400">
${phrase}
</div>
<div class="mt-2 text-sm text-gray-600">
<i class="fas fa-info-circle ml-1"></i>
تأكد من وجود هذا النص في الترجمة.
</div>
</li>`);
}
}
// الخطوة 4: اختلافات المعنى
const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g;
while ((match = meaningRegex.exec(analysisOutput)) !== null) {
const phrase = match[1].trim();
if (phrase) {
const lineNum = getLineNumber(sourceText, phrase);
steps.push(`
<li class="mb-3 pb-3 border-b border-gray-200">
<div class="font-bold text-red-700 mb-2 flex items-center">
<i class="fas fa-exclamation-circle text-red-500 ml-2"></i>
<span>اختلاف معنى:</span>
<span class="mr-2 bg-red-100 text-red-800 text-sm px-2 py-1 rounded">السطر ${lineNum}</span>
</div>
<div class="bg-red-50 p-3 rounded-lg border-r-4 border-red-400">
${phrase}
</div>
<div class="mt-2 text-sm text-gray-600">
<i class="fas fa-info-circle ml-1"></i>
راجع ترجمة هذا النص للتأكد من نقل المعنى الصحيح.
</div>
</li>`);
}
}
// إذا لم يكن هناك اختلافات
if (steps.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>`;
}
return `
<div class="bg-gray-50 p-4 rounded-xl border border-gray-200 mb-4">
<h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-clipboard-check text-blue-600 ml-2"></i>
ملخص الاختلافات
</h4>
<p class="text-gray-600">تم العثور على ${steps.length} اختلاف يحتاج إلى مراجعة.</p>
</div>
<ol class="list-none space-y-4">${steps.join('')}</ol>`;
}
// دالة مساعدة للتحقق إذا كان النص مضمن جزئيا
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;
}
/* =====================================
التبديل بين عرض وإخفاء المسودة
===================================== */
document.getElementById('toggleDraftBtn').addEventListener('click', function() {
const draftSection = document.getElementById('fullTextDraftSection');
draftSection.classList.toggle('hidden');
// تغيير نص الزر بناء على حالة العرض
const isHidden = draftSection.classList.contains('hidden');
this.innerHTML = isHidden ?
'<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() {
alert('سيتم تنفيذ تنزيل التقرير بصيغة Word قريبًا');
});
/* =====================================
معالجة OCR للملفات
===================================== */
function processFileForOCR(type) {
const fileInput = type === 'source' ? document.getElementById('sourceFile') : document.getElementById('targetFile');
const file = fileInput.files[0];
if (!file) {
alert('الرجاء اختيار ملف أولاً');
return;
}
if (file.type === 'application/pdf') {
processPDF(file, type);
} else if (file.type.startsWith('image/')) {
processImage(file, type);
} else {
alert('يرجى اختيار ملف PDF أو صورة للمعالجة');
}
}
async function processPDF(file, type) {
try {
currentProcessingMode = type;
document.getElementById('processingStatus').classList.remove('hidden');
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: false
});
}
document.getElementById('progressBar').style.width = '100%';
document.getElementById('pdfPagesCard').classList.remove('hidden');
document.getElementById('resultsCard').classList.add('hidden');
displayPDFPages();
} catch (error) {
console.error('خطأ في معالجة ملف PDF:', error);
alert('حدث خطأ أثناء معالجة ملف PDF');
document.getElementById('processingStatus').classList.add('hidden');
}
}
function processImage(file, type) {
currentProcessingMode = type;
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري معالجة الصورة...';
document.getElementById('progressBar').style.width = '30%';
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
documentPages = [{
pageNum: 1,
imageData: e.target.result,
selected: true
}];
selectedPages = [0];
document.getElementById('progressBar').style.width = '100%';
document.getElementById('pdfPagesCard').classList.remove('hidden');
document.getElementById('resultsCard').classList.add('hidden');
displayPDFPages();
};
img.src = e.target.result;
};
reader.onerror = function() {
console.error('خطأ في قراءة الصورة');
alert('حدث خطأ أثناء قراءة الصورة');
document.getElementById('processingStatus').classList.add('hidden');
};
reader.readAsDataURL(file);
}
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 base64Image = pageData.imageData.split(',')[1];
// إرسال الصورة إلى API الـ OCR
const formData = new FormData();
formData.append('image', base64Image);
formData.append('language', 'ara'); // تحديد اللغة العربية
const response = await fetch(OCR_API_URL, {
method: 'POST',
headers: {
'x-rapidapi-key': RAPIDAPI_KEY,
'x-rapidapi-host': 'ocr43.p.rapidapi.com'
},
body: formData
});
if (!response.ok) {
throw new Error(`خطأ في استخراج النص: ${response.statusText}`);
}
const data = await response.json();
if (data && data.text) {
extractedTexts.push(data.text);
extractedPageNumbers.push(pageData.pageNum);
ocrPagesCount++;
// تحديث العداد في localStorage
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();
} catch (error) {
console.error('خطأ في استخراج النص:', error);
alert('حدث خطأ أثناء استخراج النص: ' + error.message);
document.getElementById('processingStatus').classList.add('hidden');
}
}
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();
}
}
</script>
</body>
</html>