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