diff --git "a/index.html" "b/index.html" --- "a/index.html" +++ "b/index.html" @@ -1,5 +1,5 @@ - + نظام المقارنة المطور - شركة الريحان للترجمة @@ -1312,7 +1312,7 @@ .error-meaning { border-right-color: #ef4444; } - + /* حقوق الملكية - جديد */ .copyright { text-align: center; @@ -1322,12 +1322,12 @@ border-top: 1px solid #e5e7eb; margin-top: 40px; } - + .copyright a { color: #3b82f6; text-decoration: none; } - + .copyright a:hover { text-decoration: underline; } @@ -1491,6 +1491,159 @@ 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; + } @@ -1601,7 +1754,7 @@ - +
@@ -1631,14 +1784,14 @@ - +
-
- العرض الكلاسيكي -
العرض المقسم
@@ -1829,43 +1979,13 @@ العرض التفاعلي
- - -
-
- -
-

- النص المصدر (مع التعليم) -

-
-
- -
-

- النص الهدف (مع التعليم) -

-
-
-
- - -
-
- ملخص التحليل -
-
- -
-
-
- - -
+ + +
- +
@@ -1876,7 +1996,7 @@
- +
@@ -1897,7 +2017,7 @@

اضغط "التالي" للبدء في استعراض الاختلافات

- + - + - +
@@ -1943,7 +2063,7 @@
- +
توصيات المعالجة @@ -1953,7 +2073,7 @@
- +
@@ -1966,15 +2086,31 @@
- -
-
- تفاصيل الخطأ - + +
+
+
جاري معالجة البيانات...
+
0%
+
+ + + +
+
+
+ + تفاصيل الخطأ +
+
-
+
+
@@ -2002,7 +2138,7 @@
- +

جميع الحقوق محفوظة لشركة فاست برو للبرمجيات والذكاء الاصطناعي ©

@@ -2083,12 +2219,15 @@ document.getElementById('lastOcrDate').textContent = lastDate; } - // تهيئة نافذة شرح الأخطاء - initErrorPopup(); + // تهيئة النافذة المنبثقة المحسنة + initEnhancedPopup(); // تهيئة اختيار نوع الملف initFileTypeSelector(); + // إزالة العرض الكلاسيكي وتعيين العرض المقسم كافتراضي + setupViewModes(); + // إضافة أحداث لأزرار عرض PDF document.getElementById('selectAllBtn')?.addEventListener('click', selectAllPages); document.getElementById('deselectAllBtn')?.addEventListener('click', deselectAllPages); @@ -2131,7 +2270,7 @@ document.getElementById('proceedWithCorrectedText')?.addEventListener('click', function() { // إخفاء معاينة التصحيح document.getElementById('correctionPreview').classList.remove('active'); - + // استخدام النص المُصحح في المكان المناسب if (currentProcessingMode === 'source') { document.getElementById('sourceText').value = correctedText; @@ -2144,7 +2283,7 @@ addError('تم تطبيق التصحيحات على المصدر الإضافي', 'info'); } }); - + // إضافة أحداث التلقائية لمعالجة الملفات document.getElementById('sourceFile').addEventListener('change', function(event) { const file = event.target.files[0]; @@ -2152,14 +2291,14 @@ 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) { @@ -2173,7 +2312,7 @@ // إزالة الفئة 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'); @@ -2186,13 +2325,13 @@ 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); }); @@ -2202,7 +2341,7 @@ document.getElementById('toggleDraftBtn').addEventListener('click', function() { const draftSection = document.getElementById('fullTextDraftSection'); draftSection.classList.toggle('hidden'); - + // تغيير نص الزر this.innerHTML = draftSection.classList.contains('hidden') ? ' عرض مسودة التحليل' : @@ -2216,21 +2355,21 @@ 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, @@ -2239,17 +2378,17 @@ 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("حدث خطأ أثناء إنشاء التقرير"); @@ -2263,31 +2402,61 @@ }); }); + /* ===================================== + إعداد طرق العرض وإزالة العرض الكلاسيكي + ===================================== */ + 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'); - + // إضافة مستمعي الأحداث لخيارات نوع الملف 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(); }); - + // إضافة حدث للراديو بوتن نفسه const radioInput = option.querySelector('input[type="radio"]'); radioInput.addEventListener('change', function() { @@ -2299,7 +2468,7 @@ } }); }); - + // تحديث الوصف الأولي updateFileTypeDescription(); } @@ -2309,7 +2478,7 @@ ===================================== */ function updateFileTypeDescription() { const descriptionElement = document.getElementById('fileTypeDescription'); - + if (selectedFileType === 'normal') { descriptionElement.innerHTML = ` @@ -2324,55 +2493,255 @@ } /* ===================================== - تصحيح النصوص للمستندات الرسمية - جديد + تهيئة النافذة المنبثقة المحسنة ===================================== */ - async function correctOfficialDocument(text, targetType) { - try { - // عرض حالة التصحيح - document.getElementById('correctionStatus').classList.add('active'); - document.getElementById('correctionMessage').textContent = 'جاري تحليل النص وتحديد الدولة...'; - document.getElementById('correctionProgressBar').style.width = '10%'; + 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'); - // حفظ النص الأصلي - originalTextBeforeCorrection = text; + // إضافة حدث لزر الإغلاق + closeBtn.addEventListener('click', closeEnhancedPopup); + confirmBtn.addEventListener('click', closeEnhancedPopup); - // تحديد الدولة أولاً - detectedCountry = await detectCountryFromText(text); + // إضافة حدث للنقر على الخلفية + overlay.addEventListener('click', closeEnhancedPopup); - document.getElementById('correctionMessage').textContent = `تم تحديد الدولة: ${detectedCountry}. جاري تطبيق قواعد التصحيح...`; - document.getElementById('correctionProgressBar').style.width = '40%'; + // إضافة مستمع للنقر على مفتاح Escape + document.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeEnhancedPopup(); + } + }); + } + + /* ===================================== + إغلاق النافذة المنبثقة المحسنة + ===================================== */ + function closeEnhancedPopup() { + const popup = document.getElementById('enhancedErrorPopup'); + const overlay = document.getElementById('popup-overlay'); - // تطبيق التصحيحات بناءً على الدولة - const correctionResult = await applyCorrectionRules(text, detectedCountry); + 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'); - document.getElementById('correctionProgressBar').style.width = '80%'; + // تعيين العنوان حسب نوع الخطأ + let typeIcon = ''; + let typeClass = ''; + let typeTitle = ''; - // حفظ النص المُصحح والتغييرات - correctedText = correctionResult.correctedText; - correctionChanges = correctionResult.changes; + if (errorType === 'number') { + typeTitle = 'خطأ في الأرقام'; + typeIcon = ''; + typeClass = 'border-yellow-400'; + } else if (errorType === 'missing') { + typeTitle = 'نص مفقود'; + typeIcon = ''; + typeClass = 'border-blue-400'; + } else if (errorType === 'meaning') { + typeTitle = 'اختلاف في المعنى'; + typeIcon = ''; + typeClass = 'border-red-400'; + } else { + typeTitle = 'تفاصيل الخطأ'; + typeIcon = ''; + typeClass = 'border-blue-400'; + } - document.getElementById('correctionMessage').textContent = `تم تطبيق ${correctionChanges.length} تصحيح بنجاح`; - document.getElementById('correctionProgressBar').style.width = '100%'; + title.innerHTML = `${typeIcon} ${typeTitle}`; + // إعداد المحتوى المحسن + let contentHTML = ` +
+

النص المحدد:

+
${errorText}
+
+ +
+

تفسير الخطأ:

+
${explanation}
+
+ `; + + // إضافة أمثلة إذا كانت متوفرة + if (examples) { + contentHTML += ` +
+

أمثلة توضيحية:

+
+
+
+ مثال خاطئ: +
+
${examples.incorrect}
+
+
+
+ مثال صحيح: +
+
${examples.correct}
+
+
+
+ `; + } + + // إضافة توضيح مرئي (صورة) حسب نوع الخطأ + 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 += ` +
+

توضيح مرئي:

+ توضيح للخطأ +
+ `; + } + + // إضافة نصائح للتصحيح + let tips = ''; + if (errorType === 'number') { + tips = ` +
  • تأكد من تطابق الأرقام بين النص المصدر والنص الهدف.
  • +
  • انتبه للأرقام بمختلف أنظمة كتابتها (عربي، هندي، إنجليزي).
  • +
  • تحقق من تنسيق الأرقام مثل استخدام الفواصل العشرية.
  • + `; + } else if (errorType === 'missing') { + tips = ` +
  • أضف النص المفقود إلى الترجمة.
  • +
  • تأكد من ترجمة جميع عناصر النص المصدر بشكل كامل.
  • +
  • ابحث عن أي فقرات أو جمل منسية في النص الهدف.
  • + `; + } else if (errorType === 'meaning') { + tips = ` +
  • راجع معنى النص في المصدر والهدف للتأكد من التطابق.
  • +
  • استخدم مصطلحات دقيقة ومناسبة للسياق.
  • +
  • تجنب الترجمة الحرفية التي قد تؤدي إلى فقدان المعنى الأصلي.
  • + `; + } + + if (tips) { + contentHTML += ` +
    +

    + نصائح للتصحيح: +

    +
      + ${tips} +
    +
    + `; + } + + 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 { + // عرض حالة التصحيح + 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 applyCorrectionRules(text, detectedCountry); + + 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; // إرجاع النص الأصلي في حالة الخطأ } } /* ===================================== - تحديد الدولة من النص - جديد + تحديد الدولة من النص - محسن ===================================== */ async function detectCountryFromText(text) { try { @@ -2380,9 +2749,9 @@ 1. المصطلحات المستخدمة 2. أسلوب الكتابة الرسمية 3. الكلمات المميزة للدولة - + أجب بكلمة واحدة فقط اسم الدولة بالعربية. - + النص: ${text.substring(0, 500)}`; @@ -2411,12 +2780,12 @@ 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 'مصر'; // قيمة افتراضية @@ -2424,7 +2793,7 @@ } /* ===================================== - تطبيق قواعد التصحيح بناءً على الدولة - جديد + تطبيق قواعد التصحيح بناءً على الدولة - محسن ===================================== */ async function applyCorrectionRules(text, country) { try { @@ -2440,20 +2809,22 @@ - "الهويه" → "الهوية" - "البيناات" → "البيانات" - "الشحصية" → "الشخصية" - + - "الأحوال الشخصية" → "الأحوال الشخصية" + - "اسم الشخس" → "اسم الشخص" + 2. صحح الأخطاء الإملائية في المصطلحات الرسمية حسب معايير ${country} - + 3. لا تُغيّر أي بيانات شخصية (الأسماء، التواريخ، الأرقام، العناوين) - + 4. احتفظ بالتنسيق الأصلي للنص تماماً - + أعِد النص المُصحح مع قائمة بالتغييرات في هذا الشكل: النص المُصحح: [النص هنا] - + التغييرات المطبقة: - [الكلمة الخطأ] → [الكلمة الصحيحة] - + النص الأصلي: ${text}`; @@ -2482,18 +2853,18 @@ 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(); // استخراج التغييرات من النص @@ -2509,12 +2880,12 @@ return null; }).filter(change => change !== null); } - + return { correctedText: correctedText, changes: changes }; - + } catch (error) { console.error('خطأ في تطبيق قواعد التصحيح:', error); throw error; @@ -2522,12 +2893,12 @@ } /* ===================================== - عرض معاينة التصحيحات - جديد + عرض معاينة التصحيحات - محسن ===================================== */ function showCorrectionPreview() { const previewElement = document.getElementById('correctionPreview'); const changesElement = document.getElementById('correctionChanges'); - + if (correctionChanges.length === 0) { changesElement.innerHTML = '

    لم يتم العثور على أخطاء تحتاج إلى تصحيح في الثوابت

    '; } else { @@ -2543,22 +2914,25 @@ }); 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); @@ -2575,27 +2949,30 @@ } else { // إذا كان نوع الملف غير مدعوم document.getElementById('processingStatus').classList.add('hidden'); + hideLoadingIndicator(); alert('نوع الملف غير مدعوم. يرجى تحميل ملف PDF أو صورة أو Word أو Excel.'); } } /* ===================================== - معالجة ملف PDF - محدثة + معالجة ملف 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 }); @@ -2603,44 +2980,50 @@ 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(); @@ -2651,14 +3034,16 @@ 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; @@ -2667,241 +3052,274 @@ console.error('خطأ في قراءة الصورة'); alert('حدث خطأ أثناء قراءة الصورة'); document.getElementById('processingStatus').classList.add('hidden'); + hideLoadingIndicator(); }; reader.readAsDataURL(file); } - + /* ===================================== - معالجة ملف Word (DOCX) - محدثة + معالجة ملف 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(); - - // استخدام مكتبة Mammoth لاستخراج النص من DOCX - mammoth.extractRawText({ arrayBuffer: arrayBuffer }) - .then(async function(result) { - document.getElementById('progressBar').style.width = '70%'; + + // تحسين استخراج النص من DOCX مع محاولة معالجة المستندات الرسمية + updateLoadingProgress('جاري استخراج النص من ملف Word...', '50%'); + + try { + // استخدام مكتبة Mammoth لاستخراج النص من DOCX + const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer }); - let extractedText = result.value; + 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; - alert('تم استخراج النص من ملف Word وإضافته كنص مصدر'); + addError('تم استخراج النص من ملف Word وإضافته كنص مصدر', 'info'); } else if (targetType === 'target') { document.getElementById('targetText').value = extractedText; - alert('تم استخراج النص من ملف Word وإضافته كنص هدف'); + addError('تم استخراج النص من ملف Word وإضافته كنص هدف', 'info'); } else if (targetType === 'extra') { document.getElementById('sourceExtraText').value = extractedText; - alert('تم استخراج النص من ملف Word وإضافته كمصدر إضافي'); + 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); - document.getElementById('processingStatus').classList.add('hidden'); - }) - .catch(function(error) { - console.error('خطأ في معالجة ملف Word:', error); - alert('حدث خطأ أثناء معالجة ملف Word'); - document.getElementById('processingStatus').classList.add('hidden'); - }); + // محاولة استخدام طريقة بديلة لاستخراج النص + 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'); + alert('حدث خطأ أثناء معالجة ملف Word: ' + error.message); document.getElementById('processingStatus').classList.add('hidden'); + hideLoadingIndicator(); } } - + /* ===================================== - عرض النص المستخرج من ملف Word - محدثة + استخراج نص بديل من ملف 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 = `

    النص المستخرج من ملف Word${correctionApplied ? ' (مُصحح)' : ''}

    ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}
    `; - + document.getElementById('resultText').textContent = text; } /* ===================================== - معالجة ملف Excel + معالجة ملف 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); - } - - /* ===================================== - تهيئة نافذة شرح الأخطاء - ===================================== */ - function initErrorPopup() { - // إضافة حدث لزر الإغلاق - document.querySelector('.error-popup-close').addEventListener('click', function() { - const popup = document.getElementById('errorPopup'); - popup.classList.remove('show'); - setTimeout(() => { - popup.style.display = 'none'; - }, 300); - }); - - // إغلاق النافذة عند النقر خارجها - window.addEventListener('click', function(event) { - const popup = document.getElementById('errorPopup'); - if (event.target === popup) { - popup.classList.remove('show'); - setTimeout(() => { - popup.style.display = 'none'; - }, 300); - } - }); - } - - /* ===================================== - دالة عرض شرح الخطأ - محسنة لتكون أكثر ودية - ===================================== */ - function showErrorExplanation(errorType, errorText, explanation) { - const popup = document.getElementById('errorPopup'); - const title = document.getElementById('errorPopupTitle'); - const content = document.getElementById('errorPopupContent'); - - // تعيين العنوان حسب نوع الخطأ - let typeIcon = ''; - let typeClass = ''; - - if (errorType === 'number') { - title.textContent = 'خطأ في الأرقام'; - typeIcon = ''; - typeClass = 'bg-yellow-50 border-yellow-400'; - } else if (errorType === 'missing') { - title.textContent = 'نص مفقود'; - typeIcon = ''; - typeClass = 'bg-blue-50 border-blue-400'; - } else if (errorType === 'meaning') { - title.textContent = 'اختلاف في المعنى'; - typeIcon = ''; - typeClass = 'bg-red-50 border-red-400'; - } else { - title.textContent = 'تفاصيل الخطأ'; - typeIcon = ''; - typeClass = 'bg-blue-50 border-blue-400'; - } - // إعداد المحتوى - content.innerHTML = ` -
    - - ${typeIcon} النص المحدد: - -
    ${errorText}
    -
    -
    - - الشرح: - -
    ${explanation}
    -
    -
    - - انقر خارج النافذة أو اضغط على زر الإغلاق للرجوع -
    - `; - - // عرض النافذة مع تأثير التلاشي - popup.style.display = 'block'; - setTimeout(() => { - popup.classList.add('show'); - }, 10); + reader.readAsArrayBuffer(file); } /* ===================================== - عرض صفحات PDF + عرض صفحات 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); @@ -2914,8 +3332,9 @@ } }); }); - + document.getElementById('processingStatus').classList.add('hidden'); + hideLoadingIndicator(); } /* ===================================== @@ -2927,7 +3346,7 @@ page.selected = true; selectedPages.push(index); }); - + document.querySelectorAll('.pdf-page').forEach(pageDiv => { pageDiv.classList.add('selected'); }); @@ -2938,95 +3357,115 @@ 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) * 100}%`; - - // استخدام API الـ OCR + 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%'); - document.getElementById('progressBar').style.width = '100%'; - // دمج النصوص المستخرجة let combinedText = extractedTexts.join('\n\n'); - + // تطبيق التصحيح إذا كان الملف مستنداً رسمياً if (selectedFileType === 'official') { + updateLoadingProgress('جاري تصحيح النص المستخرج للمستند الرسمي...', '85%'); combinedText = await correctOfficialDocument(combinedText, currentProcessingMode); } - + + updateLoadingProgress('جاري تحضير النتائج...', '90%'); + // عرض النصوص المستخرجة displayExtractedTexts(); - + // استخدام النص تلقائيًا if (currentProcessingMode === 'source') { - useOcrText('source'); + document.getElementById('sourceText').value = combinedText; + addError('تم إضافة النص المستخرج كنص مصدر بنجاح', 'info'); } else if (currentProcessingMode === 'target') { - useOcrText('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', @@ -3036,23 +3475,28 @@ }, 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) { @@ -3066,28 +3510,28 @@ } /* ===================================== - عرض النصوص المستخرجة - محدثة + عرض النصوص المستخرجة - محسنة ===================================== */ 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 = `

    الصفحة ${extractedPageNumbers[index]}${correctionApplied ? ' (مُصححة)' : ''}

    ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}
    `; - + resultPreview.appendChild(pagePreview); }); - + // عرض النص الكامل (المُصحح إذا لزم الأمر) const finalText = correctionApplied ? correctedText : extractedTexts.join('\n\n'); document.getElementById('resultText').textContent = finalText; @@ -3098,19 +3542,19 @@ ===================================== */ 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('تم نسخ النص'); } @@ -3121,27 +3565,27 @@ 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); } @@ -3151,30 +3595,30 @@ ===================================== */ 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)); @@ -3183,54 +3627,54 @@ 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, @@ -3243,37 +3687,37 @@ 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() { @@ -3288,7 +3732,7 @@ cropCanvas.width, cropCanvas.height ); - + // تحديث صورة الخلفية fabric.Image.fromURL(cropCanvas.toDataURL(), function(newImg) { fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); @@ -3296,7 +3740,7 @@ width: cropCanvas.width, height: cropCanvas.height }); - + updateModifiedImage(); }); }; @@ -3305,26 +3749,26 @@ 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(); }); @@ -3332,45 +3776,45 @@ 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(); }); } @@ -3388,7 +3832,7 @@ function renderSheetSelector(sheetNames, targetType) { const container = document.getElementById('sheetSelectorContainer'); container.innerHTML = ''; - + sheetNames.forEach(sheetName => { const btn = document.createElement('div'); btn.className = 'sheet-selector'; @@ -3400,13 +3844,13 @@ }; container.appendChild(btn); }); - + // تفعيل الورقة الأولى const firstSheet = container.querySelector('.sheet-selector'); if (firstSheet) { firstSheet.classList.add('active'); } - + // إظهار قسم معاينة Excel document.getElementById('excelPreviewCard').classList.remove('hidden'); } @@ -3416,18 +3860,18 @@ ===================================== */ 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); } @@ -3437,35 +3881,35 @@ ===================================== */ function renderExcelTable(data) { const container = document.getElementById('excelContent'); - + if (!data || data.length === 0) { container.innerHTML = '

    لا توجد بيانات في هذه الورقة

    '; return; } - + let html = ''; - + // إنشاء الترويسة باستخدام الصف الأول أو محتوى عمود const headerRow = data[0]; - + for (let i = 0; i < headerRow.length; i++) { html += ``; } - + html += ''; - + // إضافة صفوف البيانات for (let i = 1; i < data.length; i++) { html += ''; const row = data[i]; - + for (let j = 0; j < headerRow.length; j++) { html += ``; } - + html += ''; } - + html += '
    ${headerRow[i] || 'عمود ' + (i+1)}
    ${row[j] !== undefined ? row[j] : ''}
    '; container.innerHTML = html; } @@ -3478,10 +3922,10 @@ alert('لم يتم تحميل بيانات Excel'); return; } - + // تحويل البيانات إلى نص let textContent = ''; - + // تخطي صف الترويسة وبدء من الصف 1 for (let i = 1; i < excelData.data.length; i++) { const row = excelData.data[i]; @@ -3489,7 +3933,7 @@ textContent += row.join('\t') + '\n'; } } - + // تحديث النص المصدر أو الهدف if (target === 'source') { document.getElementById('sourceText').value = textContent; @@ -3498,7 +3942,7 @@ } else if (target === 'extra') { document.getElementById('sourceExtraText').value = textContent; } - + alert('تم استخدام محتوى Excel بنجاح'); } @@ -3517,58 +3961,64 @@ function splitIntoSentences(text) { // تعبير منتظم محسن للتعامل مع نهايات الجمل بشكل أفضل const sentences = text.split(/(?<=[.!?])\s+|(?<=\n\s*\n)/g); - + // تنظيف النتائج من الجمل الفارغة return sentences.filter(s => s.trim()); } /* ===================================== - دالة معالجة عملية التحليل عند الضغط على الزر - محدثة + دالة معالجة عملية التحليل عند الضغط على الزر - محسنة مع غير متزامنة ===================================== */ document.getElementById('submitBtn').addEventListener('click', async () => { - 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; - } - 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'); - document.getElementById('processingStatus').classList.remove('hidden'); + 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, @@ -3576,38 +4026,42 @@ 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++) { - document.getElementById('progressBar').style.width = `${((i + 1) / segments.length) * 100}%`; - document.getElementById('statusText').textContent = `جاري تحليل القسم ${i+1} من ${segments.length}...`; - + 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) { @@ -3618,21 +4072,22 @@ if (totalMissingErrors > 0) addError(`- ${totalMissingErrors} نص مفقود`, 'warning'); if (totalMeaningErrors > 0) addError(`- ${totalMeaningErrors} اختلاف في المعنى`, 'warning'); } - - // تجهيز العرض الكلاسيكي - displayClassicView(sourceText, targetText); - + // تجهيز العرض المقسم displaySegmentedView(); - + // تجهيز العرض التفاعلي setupInteractiveView(); - + + updateLoadingProgress('تمت العملية بنجاح!', '100%'); + setTimeout(() => { + hideLoadingIndicator(); + }, 1000); + } catch (error) { console.error('Error during analysis:', error); addError('حدث خطأ أثناء التحليل: ' + error.message, 'error'); - } finally { - document.getElementById('processingStatus').classList.add('hidden'); + hideLoadingIndicator(); } }); @@ -3641,14 +4096,14 @@ ===================================== */ function addPreliminaryHighlights(sourceText, targetText) { let highlightedText = sourceText; - + // 1. تحديد الأرقام المحتملة const numberRegex = /(\d+|[٠١٢٣٤٥٦٧٨٩]+)/g; - highlightedText = highlightedText.replace(numberRegex, '$1'); - + highlightedText = highlightedText.replace(numberRegex, '\$1'); + // 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( @@ -3657,7 +4112,7 @@ ); } } - + return highlightedText; } @@ -3668,16 +4123,16 @@ // استخراج الكلمات المهمة (أطول من 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 أقسام + دالة تقسيم النصوص مع عدد أقصى 6 أقسام - محسنة غير متزامنة ===================================== */ async function alignTextsWithModel(sourceText, targetText) { try { @@ -3685,7 +4140,9 @@ document.getElementById('processingStatus').classList.remove('hidden'); document.getElementById('statusText').textContent = 'جاري تقسيم ومزامنة النصوص...'; document.getElementById('progressBar').style.width = '30%'; - + + updateLoadingProgress('جاري تقسيم ومزامنة النصوص...', '35%'); + // استدعاء نموذج DeepSeek للتقسيم والمزامنة const prompt = `مهمتك هي تقسيم النصين التاليين (النص المصدر والنص الهدف) إلى أقسام متوازية بحيث يتطابق كل قسم في المصدر مع ما يقابله في الهدف. @@ -3722,7 +4179,8 @@ }; document.getElementById('progressBar').style.width = '50%'; - + updateLoadingProgress('جاري معالجة تقسيم النصوص...', '40%'); + const response = await fetch(DEEPSEEK_API_URL, { method: 'POST', headers: { @@ -3737,10 +4195,11 @@ } document.getElementById('progressBar').style.width = '80%'; - + updateLoadingProgress('جاري تحليل النتائج...', '45%'); + const data = await response.json(); const result = data.choices[0].message.content; - + // استخراج JSON من النتيجة let segments = []; try { @@ -3762,19 +4221,21 @@ 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); } @@ -3787,24 +4248,24 @@ 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; } @@ -3813,56 +4274,59 @@ ===================================== */ 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. اختلافات الأرقام: ضع الرقم المختلف بين علامتي < > مع الكلمة السابقة والكلمة اللاحقة أو الجملة كاملة @@ -3913,10 +4377,10 @@ // تحليل الاختلافات وتصنيفها const numberDiffs = (analysisResult.match(/<[^<>]+>/g) || []).length; const missingTexts = (analysisResult.match(/__(.*?)__/g) || []).length; - const meaningDiffs = (analysisResult.match(/\[MEANING\](.*?)\[\/MEANING\]/g) || []).length; + const meaningDiffs = (analysisResult.match(/$$MEANING$$(.*?)$$\/MEANING$$/g) || []).length; const totalDiffs = numberDiffs + missingTexts + meaningDiffs; - + // إنشاء ملخص للتحليل let summary; if (totalDiffs === 0) { @@ -3980,11 +4444,11 @@ // إضافة تمييز مبدئي للنص المصدر const preliminaryHighlightedSource = addPreliminaryHighlights(segment.source, segment.target); - + // إنشاء محتوى القسم بطريقة محسنة const contentDiv = document.createElement('div'); contentDiv.className = 'section-content'; - + // اس��خدام تنسيق محسن للفقرات المتوازية مع التمييز المبدئي contentDiv.innerHTML = `
    @@ -4037,88 +4501,24 @@
    اختلافات الأرقام - ${analysisResult.errors.numbers} - -
    -
    - نصوص مفقودة - - ${analysisResult.errors.missing} - -
    -
    - اختلافات المعنى - - ${analysisResult.errors.meaning} - -
    -
    - `; - } - } - - /* ===================================== - عرض النتائج في العرض الكلاسيكي - محسن - ===================================== */ - function displayClassicView(sourceText, targetText) { - // دمج نتائج التحليل من جميع الأقسام - let combinedAnalysis = ''; - analysisSegments.forEach((segment, index) => { - if (index > 0) combinedAnalysis += '\n\n---\n\n'; - combinedAnalysis += segment.analysis; - }); - - // تطبيق التحديد على النصوص الكاملة مع المقارنة - document.getElementById('sourceTextReview').innerHTML = applyHighlights(sourceText, targetText, combinedAnalysis); - document.getElementById('targetTextReview').innerHTML = applyHighlights(targetText, sourceText, combinedAnalysis); - - // إعداد شرح الأخطاء بطريقة محسنة - document.getElementById('classicViewExplanation').innerHTML = generateExplanation(sourceText, targetText, combinedAnalysis); - - // إضافة مستمعي الأحداث للنصوص المحددة - setTimeout(() => { - document.querySelectorAll('.sentence-with-error').forEach(element => { - element.addEventListener('click', function() { - // الحصول على النص الكامل للجملة - const sentenceText = this.textContent; - - // تحديد نوع الخطأ من خلال الفئات داخل الجملة - let errorType = 'general'; - let errorText = sentenceText; - - // البحث عن الخطأ المحدد داخل الجملة - const numberHighlight = this.querySelector('.highlight-number'); - const missingHighlight = this.querySelector('.highlight-missing'); - const meaningHighlight = this.querySelector('.highlight-meaning'); - - if (numberHighlight) { - errorType = 'number'; - errorText = numberHighlight.textContent; - } else if (missingHighlight) { - errorType = 'missing'; - errorText = missingHighlight.textContent; - } else if (meaningHighlight) { - errorType = 'meaning'; - errorText = meaningHighlight.textContent; - } - - // إعداد شرح أكثر ودية - let explanation = ''; - - if (errorType === 'number') { - explanation = `يا صديقي، لاحظت وجود خطأ في الأرقام في هذه الجملة.

    الرقم "${errorText}" في النص المصدر لا يتطابق مع النص الهدف. يرجى التحقق من صحة الرقم والتأكد من تطابقه في كلا النصين.`; - } else if (errorType === 'missing') { - explanation = `يا صديقي، هناك نص مفقود في الترجمة!

    النص "${errorText}" موجود في المصدر ولكن لا يوجد ما يقابله في النص الهدف. يرجى إضافة هذا النص المفقود للحفاظ على اكتمال المعنى.`; - } else if (errorType === 'meaning') { - explanation = `يا صديقي، يبدو أن هناك اختلاف في المعنى!

    في الجملة "${sentenceText}" النص الذي في المصدر قد تم ترجمته بمعنى مختلف عما هو مقصود. تحتاج إلى مراجعة ترجمة "${errorText}" للتأكد من نقل المعنى الصحيح.`; - } else { - explanation = `يا صديقي، هناك خطأ في هذه الجملة: "${sentenceText}"

    يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`; - } - - showErrorExplanation(errorType, sentenceText, explanation); - }); - }); - }, 100); + ${analysisResult.errors.numbers} + +
    +
    + نصوص مفقودة + + ${analysisResult.errors.missing} + +
    +
    + اختلافات المعنى + + ${analysisResult.errors.meaning} + +
    +
    + `; + } } /* ===================================== @@ -4127,21 +4527,21 @@ 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 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)) { @@ -4161,7 +4561,7 @@ errorType = errorType || 'missing'; } } - + // تحديد اختلافات المعنى for (const phrase of meaningMatches) { if (sentence.includes(phrase)) { @@ -4171,12 +4571,12 @@ errorType = errorType || 'meaning'; } } - + // إذا كان هناك خطأ، قم بتمييز الجملة كاملة if (hasError) { return `${highlightedSentence}`; } - + return highlightedSentence; }).join(' '); @@ -4190,17 +4590,17 @@ // تقسيم النصوص إلى فقرات 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)) { @@ -4208,7 +4608,7 @@ break; } } - + // إذا كانت مفقودة تماما، نميزها بشكل مختلف if (isCompletelyMissing) { modifiedSource = modifiedSource.replace( @@ -4217,35 +4617,35 @@ ); } } - + // البحث عن الجمل المفقودة جزئيا 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(` { // تحديد أنواع الأخطاء في هذا القسم const hasNumbers = segment.errors.numbers > 0; const hasMissing = segment.errors.missing > 0; const hasMeaning = segment.errors.meaning > 0; - + // إنشاء علامات الأخطاء let tagHTML = ''; if (hasNumbers) { @@ -4286,16 +4686,16 @@ if (!hasNumbers && !hasMissing && !hasMeaning) { tagHTML = 'مطابق'; } - + // إنشاء عنصر المقطع 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 = `
    @@ -4310,29 +4710,29 @@ ${formatAnalysisText(segment.analysis)}
    `; - + 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; @@ -4343,23 +4743,44 @@ errorType = 'meaning'; errorSpecificText = meaningHighlight.textContent; } - + const sentenceNumber = this.getAttribute('data-sentence-number'); - - // إعداد شرح أكثر ودية + + // إعداد أمثلة حسب نوع الخطأ + let examples = null; + + if (errorType === 'number') { + examples = { + incorrect: `المادة ٥ من القانون، تُطبق الغرامة المقدرة ٤٥٠ دينارًا.`, + correct: `المادة ٥ من القانون، تُطبق الغرامة المقدرة ٤٥٠ دينارًا.` + }; + } else if (errorType === 'missing') { + examples = { + incorrect: `اتفق الطرفان على أن يتم تسليم الب��ائع خلال 30 يومًا من توقيع العقد.`, + correct: `اتفق الطرفان على أن يتم تسليم البضائع خلال 30 يومًا من توقيع العقد.` + }; + } else if (errorType === 'meaning') { + examples = { + incorrect: `أقرت المحكمة بإدانة المتهم.`, + correct: `أقرت المحكمة ببراءة المتهم.` + }; + } + + // إعداد شرح أكثر ودية مع أمثلة let explanation = ''; - + if (errorType === 'number') { - explanation = `يا صديقي، لاحظت وجود خطأ في الأرقام في الجملة رقم ${sentenceNumber} هذه.

    الرقم "${errorSpecificText}" في النص المصدر لا يتطابق مع النص الهدف. تحتاج لتصحيح الرقم في الترجمة.`; + explanation = `يبدو أن هناك اختلافًا في الأرقام في هذه الجملة. الرقم "${errorSpecificText}" في النص المصدر لا يتطابق مع النص الهدف. قد يكون هناك خطأ في نقل الرقم أو تحويله بين أنظمة الأرقام المختلفة (العربية، الإنجليزية، الهندية).`; } else if (errorType === 'missing') { - explanation = `يا صديقي، هناك نص مفقود في الترجمة في الجملة رقم ${sentenceNumber}!

    النص "${errorSpecificText}" موجود في المصدر ولكن لا يوجد ما يقابله في النص الهدف. يرجى إضافة هذا النص المفقود للحفاظ على اكتمال المعنى.`; + explanation = `هناك نص مفقود في الترجمة! النص "${errorSpecificText}" موجود في المصدر ولكن لم يتم ترجمته أو تضمينه في النص الهدف. هذا يؤدي إلى فقدان جزء من المعنى الأصلي.`; } else if (errorType === 'meaning') { - explanation = `يا صديقي، يبدو أن هناك اختلاف في المعنى في الجملة رقم ${sentenceNumber}!

    النص "${errorSpecificText}" في المصدر تمت ترجمته بشكل مختلف عما هو مقصود. تحتاج إلى مراجعة الترجمة للتأكد من دقتها.`; + explanation = `هناك اختلاف مهم في المعنى! النص "${errorSpecificText}" تمت ترجمته بطريقة تغير المعنى الأصلي. قد يكون هناك خطأ في فهم السياق أو اختيار المصطلحات المناسبة.`; } else { - explanation = `يا صديقي، هناك خطأ في هذه الجملة رقم ${sentenceNumber}: "${sentenceText}"

    يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`; + explanation = `هناك خطأ في هذه الجملة: "${sentenceText}"

    يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`; } - - showErrorExplanation(errorType, sentenceText, explanation); + + // استخدام النافذة المنبثقة المحسنة + showEnhancedPopup(errorType, sentenceText, explanation, examples); }); }); }, 100); @@ -4374,7 +4795,7 @@ let numberDiffCount = 0; let missingTextCount = 0; let meaningDiffCount = 0; - + // فحص جميع المقاطع analysisSegments.forEach((segment, segmentIndex) => { // استخراج اختلافات الأرقام @@ -4388,7 +4809,7 @@ }); numberDiffCount++; }); - + // استخراج النصوص المفقودة const missingMatches = Array.from(segment.analysis.matchAll(/__(.*?)__/g)); missingMatches.forEach(match => { @@ -4400,9 +4821,9 @@ }); missingTextCount++; }); - + // استخراج اختلافات المعنى - const meaningMatches = Array.from(segment.analysis.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g)); + const meaningMatches = Array.from(segment.analysis.matchAll(/$$MEANING$$(.*?)$$\/MEANING$$/g)); meaningMatches.forEach(match => { allDifferences.push({ type: 'meaning', @@ -4413,16 +4834,16 @@ 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 = `
    @@ -4442,15 +4863,15 @@ if (meaningDiffCount > 0) { recommendationsContainer.innerHTML += `

    صحح اختلافات المعنى لضمان دقة الترجمة.

    `; } - + // توصية عامة recommendationsContainer.innerHTML += `

    استخدم وضع العرض المقسم للتعديل الدقيق.

    `; } - + // تعيين أحداث النقر لأزرار التنقل document.getElementById('prevDiff').addEventListener('click', showPreviousDifference); document.getElementById('nextDiff').addEventListener('click', showNextDifference); - + // إعداد الاختلاف الأول if (allDifferences.length > 0) { currentDiffIndex = 0; @@ -4470,7 +4891,7 @@ document.getElementById('prevDiff').disabled = true; document.getElementById('nextDiff').disabled = true; } - + // إضافة توضيحات للعرض التفاعلي document.getElementById('interactiveViewExplanation').innerHTML = generateInteractiveViewExplanation(); } @@ -4482,11 +4903,11 @@ 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) { @@ -4496,20 +4917,20 @@ } else { context = text.substring(start, end); } - + // تمييز النص المطابق const highlightedContext = context.replace( new RegExp(`(${escapeRegExp(match)})`, 'g'), - `$1` + `\$1` ); - + return highlightedContext; } // عرض الاختلاف السابق function showPreviousDifference() { if (allDifferences.length === 0) return; - + currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % allDifferences.length; displayCurrentDifference(); } @@ -4517,7 +4938,7 @@ // عرض الاختلاف التالي function showNextDifference() { if (allDifferences.length === 0) return; - + currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length; displayCurrentDifference(); } @@ -4532,14 +4953,14 @@ 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'; @@ -4553,9 +4974,9 @@ typeClass = 'bg-red-100 text-red-800'; icon = 'fas fa-exclamation-circle'; } - + let highlightedContext = diff.context; - + // إضافة رقم المقطع ونوع الخطأ وعرض السياق document.getElementById('currentDiffDisplay').innerHTML = `
    @@ -4583,24 +5004,24 @@
    `; - + document.getElementById('diffCounter').textContent = `${currentDiffIndex + 1}/${allDifferences.length}`; - + // عرض النص في المصدر والهدف document.getElementById('diffDetailedView').classList.remove('hidden'); - + // عرض النص المصدر document.getElementById('diffSourceText').innerHTML = `
    ${highlightSourceText(segment.source, diff.text)}
    `; - + // عرض النص الهدف document.getElementById('diffTargetText').innerHTML = `
    ${highlightTargetText(segment.target, diff)}
    `; - + // عرض قسم المراجع إذا كان متاحا if (diff.reference) { document.getElementById('diffReference').classList.remove('hidden'); @@ -4614,7 +5035,7 @@ function highlightSourceText(sourceText, diffText) { return sourceText.replace( new RegExp(`(${escapeRegExp(diffText)})`, 'g'), - `$1` + `\$1` ); } @@ -4624,11 +5045,11 @@ // بالنسبة للنص المفقود، نحاول العثور على مكان يجب إدراجه 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 = @@ -4645,16 +5066,16 @@ // إذا لم نتمكن من تحديد المكان، نضيف علامة في النهاية markedText += ` [نص مفقود: ${diff.text}]`; } - + 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) { @@ -4664,24 +5085,24 @@ ); } } - + 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, - `$1` + `\$1` ); } } - + return markedText; } } @@ -4715,261 +5136,13 @@ text = text.replace(/المعنى/g, 'المعنى'); // تحديد علامات التمييز - text = text.replace(/<([^<>]+)>/g, '<$1>'); - text = text.replace(/__(.*?)__/g, '__$1__'); - text = text.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '$1'); + text = text.replace(/<([^<>]+)>/g, '<\$1>'); + text = text.replace(/__(.*?)__/g, '__\$1__'); + text = text.replace(/$$MEANING$$(.*?)$$\/MEANING$$/g, '\$1'); return text; } - /* ===================================== - توليد شرح منظم للعرض الكلاسيكي - ===================================== */ - function generateExplanation(sourceText, targetText, analysisOutput) { - // إضافة تحليل محسن للنصوص المفقودة - const numberErrors = []; - const missingErrors = []; - const meaningErrors = []; - - // استخراج الأخطاء من تحليل النص - const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g)); - numberMatches.forEach(match => { - numberErrors.push({ - text: match[1], - explanation: 'يا صديقي، الرقم في النص المصدر يختلف عن الرقم في النص الهدف. يجب التأكد من تطابق الأرقام في كلا النصين.' - }); - }); - - const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g)); - missingMatches.forEach(match => { - missingErrors.push({ - text: match[1], - explanation: 'يا صديقي، هذا النص موجود في المصدر ولكنه مفقود في الترجمة. يجب إضافته للحفاظ على اكتمال المعنى.' - }); - }); - - const meaningMatches = Array.from(analysisOutput.matchAll(/\[MEANING\](.*?)\[\/MEANING\]/g)); - meaningMatches.forEach(match => { - meaningErrors.push({ - text: match[1], - explanation: 'يا صديقي، هذا النص تم ترجمته بمعنى مختلف ��ما هو في النص الأصلي. تحتاج إلى مراجعة الترجمة للتأكد من نقل المعنى الصحيح.' - }); - }); - - // البحث عن النصوص المفقودة تماما - const completelyMissing = []; - const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); - - for (const paragraph of sourceParagraphs) { - if (paragraph.trim().length < 10) continue; - - // فحص إذا كانت الفقرة مفقودة تماما - if (!targetText.includes(paragraph) && !isPartiallyIncluded(paragraph, targetText)) { - if (paragraph.length > 100) { - completelyMissing.push({ - text: paragraph.substring(0, 100) + '...', - explanation: 'يا صديقي، هذا النص مفقود تمامًا في الترجمة. النص الأصلي يحتوي على هذه المعلومات المهمة التي تحتاج إلى إضافتها.' - }); - } else { - completelyMissing.push({ - text: paragraph, - explanation: 'يا صديقي، هذا النص مفقود تمامًا في الترجمة. النص الأصلي يحتوي على هذه المعلومات المهمة التي تحتاج إلى إضافتها.' - }); - } - } - } - - // إضافة النصوص المفقودة إلى قائمة الأخطاء - completelyMissing.forEach(missing => { - missingErrors.push(missing); - }); - - // إنشاء المخرجات المحسنة - if (numberErrors.length === 0 && missingErrors.length === 0 && meaningErrors.length === 0) { - return ` -
    -
    - -
    -

    النصوص متطابقة تماماً ولا توجد فروقات تحتاج للتنبيه.

    -

    تهانينا! الترجمة دقيقة ومكتملة.

    -
    `; - } - - let html = ` -
    -
    -
    ${numberErrors.length}
    -
    - - اختلافات الأرقام -
    -
    - -
    -
    ${missingErrors.length}
    -
    - - النصوص المفقودة -
    -
    - -
    -
    ${meaningErrors.length}
    -
    - - اختلافات المعنى -
    -
    -
    - -
    `; - - // إضافة مجموعة اختلافات الأرقام - if (numberErrors.length > 0) { - html += ` -
    -
    - - اختلافات الأرقام (${numberErrors.length}) -
    -
    `; - - // إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل) - const displayedErrors = numberErrors.slice(0, 5); - - displayedErrors.forEach((error, index) => { - html += ` -
    -
    - اختلاف ${index + 1} -
    -
    - ${error.text} -
    - -
    `; - }); - - // إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء - if (numberErrors.length > 5) { - html += ` -
    - - و${numberErrors.length - 5} اختلافات أخرى -
    `; - } - - html += ` -
    -
    `; - } - - // إضافة مجموعة النصوص المفقودة - if (missingErrors.length > 0) { - html += ` -
    -
    - - النصوص المفقودة (${missingErrors.length}) -
    -
    `; - - // إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل) - const displayedErrors = missingErrors.slice(0, 5); - - displayedErrors.forEach((error, index) => { - html += ` -
    -
    - نص مفقود ${index + 1} -
    -
    - ${error.text} -
    - -
    `; - }); - - // إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء - if (missingErrors.length > 5) { - html += ` -
    - - و${missingErrors.length - 5} نصوص مفقودة أخرى -
    `; - } - - html += ` -
    -
    `; - } - - // إضافة مجموعة اختلافات المعنى - if (meaningErrors.length > 0) { - html += ` -
    -
    - - اختلافات المعنى (${meaningErrors.length}) -
    -
    `; - - // إضافة 5 أخطاء كحد أقصى (أو جميعها إذا كانت أقل) - const displayedErrors = meaningErrors.slice(0, 5); - - displayedErrors.forEach((error, index) => { - html += ` -
    -
    - اختلاف معنى ${index + 1} -
    -
    - ${error.text} -
    - -
    `; - }); - - // إضافة مؤشر "المزيد" إذا كان هناك أكثر من 5 أخطاء - if (meaningErrors.length > 5) { - html += ` -
    - - و${meaningErrors.length - 5} اختلافات معنى أخرى -
    `; - } - - html += ` -
    -
    `; - } - - html += ` -
    - -
    -

    - - توصيات للتحسين -

    - -
    `; - - return html; - } - /* ===================================== توليد شرح للعرض المقسم ===================================== */ @@ -4985,7 +5158,7 @@ انقر على النص المميز للحصول على تفاصيل إضافية حول الاختلاف.

    - +

    @@ -5014,7 +5187,7 @@ استخدم أزرار "التالي" و"السابق" للتنقل بين الاختلافات المختلفة.

    - +

    @@ -5029,6 +5202,7 @@ } /* ===================================== + /* ===================================== دالة للتحقق إذا كان النص مضمن جزئيا ===================================== */ function isPartiallyIncluded(text, targetText) {