rehan / indexpro.html
joermd's picture
Create indexpro.html
b7d512c verified
raw
history blame
109 kB
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>نظام المقارنة والترجمة المطور - شركة الريحان للترجمة</title>
<!-- استيراد مكتبات Tailwind وFont Awesome وMammoth وPDF.js -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<style>
/* ================================
تنسيقات الحركات والتأثيرات
================================= */
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.animate-gradient {
background-size: 200% 200%;
animation: gradient 15s ease infinite;
}
.transition-all { transition: all 0.3s ease-in-out; }
.animate-scale { transition: transform 0.2s ease-in-out; }
.animate-scale:hover { transform: scale(1.02); }
.pulse-animation { animation: pulse 2s infinite; }
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(156,39,176,0.4); }
70% { box-shadow: 0 0 0 10px rgba(156,39,176,0); }
100% { box-shadow: 0 0 0 0 rgba(156,39,176,0); }
}
/* ================================
تنسيقات النصوص والتحديد
================================= */
.text-comparison { line-height: 1.8; white-space: pre-wrap; }
.highlight-number {
background-color: #FDE68A;
padding: 0 4px;
border-radius: 3px;
font-weight: bold;
}
.highlight-missing {
background-color: #BFDBFE;
padding: 0 4px;
border-radius: 3px;
font-style: italic;
}
.highlight-meaning {
background-color: #fecaca;
color: #B91C1C;
padding: 0 4px;
border-radius: 3px;
font-weight: bold;
}
/* ================================
تنسيق عرض السطور في المعاينة
================================= */
.split-view {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.line-item {
display: flex;
align-items: flex-start;
margin-bottom: 0.5rem;
}
.line-number {
width: 30px;
font-weight: bold;
color: #4B5563;
flex-shrink: 0;
}
.line-text { flex: 1; }
/* ================================
تحسين تنسيق البطاقات والعناصر
================================= */
.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: 8px;
overflow: hidden;
background-color: #f9fafb;
}
.section-header {
background-color: #f3f4f6;
padding: 12px 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
}
.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;
}
.paragraph-section {
border-left: 3px solid #d1d5db;
padding-left: 12px;
margin-bottom: 16px;
}
/* ================================
تنسيقات جديدة للشعار والعرض المحسن
================================= */
.logo-container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
}
.logo {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-left: 1rem;
overflow: hidden;
}
.company-name {
font-size: 1.5rem;
font-weight: bold;
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
/* طريقة العرض الجديدة للنصوص المقسمة */
.segment-comparison {
border: 1px solid #e5e7eb;
border-radius: 8px;
margin-bottom: 1rem;
overflow: hidden;
}
.segment-header {
background-color: #f3f4f6;
padding: 0.75rem 1rem;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
}
.segment-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
background-color: #e5e7eb;
}
.segment-source, .segment-target {
background-color: #fff;
padding: 1rem;
}
.segment-source {
background-color: #f0f9ff;
}
.segment-target {
background-color: #fdf2f8;
}
.segment-notes {
grid-column: span 2;
background-color: #fffbeb;
padding: 0.75rem 1rem;
border-top: 1px solid #e5e7eb;
font-size: 0.9rem;
}
.segment-tag {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
margin-right: 0.5rem;
}
.tag-error {
background-color: #fee2e2;
color: #b91c1c;
}
.tag-warning {
background-color: #fef3c7;
color: #92400e;
}
.tag-info {
background-color: #dbeafe;
color: #1e40af;
}
.view-tabs {
display: flex;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 1rem;
}
.view-tab {
padding: 0.75rem 1.5rem;
cursor: pointer;
border-bottom: 2px solid transparent;
font-weight: 500;
}
.view-tab.active {
border-bottom-color: #3b82f6;
color: #3b82f6;
}
.view-content {
display: none;
}
.view-content.active {
display: block;
}
/* ================================
تنسيقات OCR حسب النموذج الجديد
================================= */
/* تصميم شبكة عرض صفحات PDF */
.pdf-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
margin-top: 15px;
}
.pdf-page {
border: 1px solid #ddd;
border-radius: 5px;
padding: 5px;
position: relative;
cursor: pointer;
transition: all 0.2s;
}
.pdf-page:hover {
transform: translateY(-3px);
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
}
.pdf-page.selected {
border: 2px solid #3b82f6;
}
.pdf-page img {
width: 100%;
height: auto;
border-radius: 3px;
}
.page-number {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.6);
color: white;
text-align: center;
font-size: 12px;
padding: 2px;
}
/* تنسيقات النص المستخرج */
.result-text {
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
direction: rtl;
border: 1px solid #ddd;
padding: 10px;
border-radius: 5px;
background-color: #f8f9fa;
}
/* تنسيقات معاينة الصفحات */
.page-preview {
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
}
.page-preview h4 {
background-color: #f0f8ff;
padding: 5px;
border-radius: 3px;
margin-bottom: 10px;
}
/* شريط التقدم والمؤشرات */
.progress {
height: 0.5rem;
border-radius: 9999px;
overflow: hidden;
background-color: #e5e7eb;
}
.progress-bar {
height: 100%;
border-radius: 9999px;
background-color: #3b82f6;
transition: width 0.3s ease;
}
/* تنسيقات الإحصائيات */
.stats-badge {
background-color: #3b82f6;
color: white;
font-size: 14px;
padding: 5px 10px;
border-radius: 20px;
margin-right: 10px;
}
.stats-container {
display: flex;
align-items: center;
margin-bottom: 15px;
}
/* تنسيقات خاصة بترجمة الريحان */
.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);
}
</style>
</head>
<body class="bg-white">
<div class="min-h-screen pb-12">
<!-- ============ رأس الصفحة ============ -->
<header class="bg-gradient-to-r from-blue-600 to-blue-800 text-white py-10 mb-10 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 mr-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="48" height="48">
<image xlink:href="https://placehold.co/100x100/3B82F6/white?text=ر" width="100" height="100"/>
</svg>
</div>
<div class="company-name text-2xl font-bold">شركة الريحان للترجمة</div>
</div>
</div>
<h1 class="text-5xl font-bold text-center mb-4 animate-scale">النظام المراجع الذكي</h1>
<p class="text-center text-xl text-blue-100 opacity-90">مقارنة وتحليل النصوص - المصدر مرجع أساسي</p>
</div>
</header>
<!-- ============ شرح البرنامج التعريفي ============ -->
<section id="programExplanation" class="max-w-6xl mx-auto px-4 mb-8">
<div class="bg-white-classic rounded-2xl shadow-lg p-8 border border-gray-100">
<p class="text-lg text-gray-800">
هذا البرنامج مصمم لمقارنة النصوص التقنية وتحليلها بدقة عالية، مع تحديد الاختلافات في الأرقام، النصوص المفقودة واختلافات المعنى. يمكنك استخدام نظام OCR لاستخراج النصوص من ملفات PDF والصور، مع إمكانية تحرير الصور وقصها وتدويرها قبل الاستخراج.
</p>
</div>
</section>
<!-- ============ المحتوى الرئيسي ============ -->
<main class="max-w-6xl mx-auto px-4">
<!-- قسم رفع الملفات -->
<div class="bg-white-classic rounded-2xl shadow-lg p-8 border border-gray-100 hover:shadow-xl transition-all animate-scale mb-8 card-hover">
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3">
<i class="fas fa-file-upload text-blue-600 ml-2"></i> تحميل الملفات
</h2>
<!-- إحصائيات OCR -->
<div class="stats-container mb-4">
<div class="stats-badge">
عدد الصفحات المعالجة: <span id="ocrCounter">0</span>
</div>
<div class="stats-badge">
تاريخ آخر معالجة: <span id="lastOcrDate">-</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- ملف السورس -->
<div class="text-center">
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100">
<label class="cursor-pointer block">
<input type="file" id="sourceFile" accept=".docx,.pdf,.jpg,.jpeg,.png" class="hidden">
<i class="fas fa-file-upload text-5xl text-blue-500 mb-4"></i>
<span class="text-lg text-blue-600 group-hover:text-blue-700">ملف السورس</span>
</label>
</div>
<div class="mt-2">
<button id="processSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
معالجة OCR للسورس
</button>
</div>
</div>
<!-- ملف التارجت -->
<div class="text-center">
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100">
<label class="cursor-pointer block">
<input type="file" id="targetFile" accept=".docx,.pdf,.jpg,.jpeg,.png" class="hidden">
<i class="fas fa-file-download text-5xl text-blue-500 mb-4"></i>
<span class="text-lg text-blue-600 group-hover:text-blue-700">ملف التارجت</span>
</label>
</div>
<div class="mt-2">
<button id="processTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
معالجة OCR للتارجت
</button>
</div>
</div>
</div>
<!-- حالة المعالجة -->
<div id="processingStatus" class="hidden mt-4">
<div class="alert alert-info bg-blue-50 border border-blue-200 rounded-xl p-4 my-4">
<div class="flex items-center">
<div class="animate-spin h-6 w-6 border-4 border-blue-600 rounded-full border-t-transparent ml-3"></div>
<span id="statusText">جاري معالجة الملف...</span>
</div>
</div>
<div class="progress mt-2">
<div id="progressBar" class="progress-bar" style="width: 0%"></div>
</div>
</div>
<!-- عرض صفحات PDF -->
<div id="pdfPagesCard" class="hidden mt-6">
<h3 class="text-xl font-bold mb-4 text-gray-800">صفحات الملف</h3>
<p>اختر الصفحات التي تريد معالجتها (انقر للتحديد)</p>
<div class="mb-3">
<button id="selectAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-3 rounded text-sm ml-2">تحديد الكل</button>
<button id="deselectAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-3 rounded text-sm">إلغاء تحديد الكل</button>
</div>
<div id="pdfPagesContainer" class="pdf-grid"></div>
<div class="mt-4 flex items-center space-x-2">
<div class="edit-toolbar flex flex-wrap gap-2">
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="rotateLeft">
<i class="fas fa-undo ml-1"></i> تدوير لليسار
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="rotateRight">
<i class="fas fa-redo ml-1"></i> تدوير لليمين
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="flipHorizontal">
<i class="fas fa-arrows-alt-h ml-1"></i> قلب أفقي
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="flipVertical">
<i class="fas fa-arrows-alt-v ml-1"></i> قلب عمودي
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="cropImage">
<i class="fas fa-crop ml-1"></i> قص الصورة
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="resetImage">
<i class="fas fa-sync-alt ml-1"></i> إعادة ضبط
</button>
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="improveContrast">
<i class="fas fa-adjust ml-1"></i> تحسين التباين
</button>
</div>
</div>
<div class="mt-3">
<button id="extractTextBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
<i class="fas fa-magic ml-2"></i> استخراج النص
</button>
</div>
<!-- محرر الصور -->
<div id="imageEditor" class="hidden mt-4">
<div class="border border-gray-300 rounded-lg bg-gray-100 p-2">
<canvas id="imageCanvas" class="max-w-full"></canvas>
</div>
</div>
</div>
<!-- عرض النتائج -->
<div id="resultsCard" class="hidden mt-6">
<h3 class="text-xl font-bold mb-4 text-gray-800">النص المستخرج</h3>
<div id="resultPreview"></div>
<div id="resultTextContainer" class="mt-4">
<div class="flex justify-between items-center mb-2">
<h4 class="font-bold text-gray-800">النص:</h4>
<button id="copyTextBtn" class="bg-blue-100 hover:bg-blue-200 text-blue-800 px-3 py-1 rounded-lg text-sm">
<i class="far fa-copy ml-1"></i> نسخ النص
</button>
</div>
<div id="resultText" class="result-text">
لم يتم استخراج نص بعد.
</div>
</div>
<div class="mt-3 flex space-x-2">
<button id="useAsSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
استخدام النص كنص مصدر
</button>
<button id="useAsTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all">
استخدام النص كنص هدف
</button>
<button id="downloadTextBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition-all">
تنزيل النص
</button>
</div>
</div>
</div>
<!-- قسم إدخال النصوص يدويًا وخيار استخدام قاعدة المصطلحات -->
<div class="bg-white-classic rounded-2xl shadow-lg p-8 border border-gray-100 hover:shadow-xl transition-all animate-scale mb-8 card-hover">
<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">
<input type="checkbox" id="terminologyCheck" class="mr-2">
<label for="terminologyCheck" class="text-lg text-gray-700">استخدام قاعدة المصطلحات</label>
</div>
</div>
</div>
<!-- قسم المصادر الإضافية -->
<div class="bg-white-classic rounded-2xl shadow-lg p-8 border border-gray-100 hover:shadow-xl transition-all animate-scale mb-8 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" 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">
<div class="flex items-center justify-center">
<i class="fas fa-sync-alt ml-2"></i> تحليل النصوص
</div>
</button>
<!-- شاشة عرض نتائج التحليل مع علامات التبويب للطرق المختلفة -->
<div id="resultSection" class="bg-white-classic rounded-2xl shadow-lg p-8 border border-gray-100 hover:shadow-xl 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="view-tabs">
<div class="view-tab active" data-tab="classic-view">طريقة العرض الكلاسيكية</div>
<div class="view-tab" data-tab="segment-view">طريقة العرض المقسمة</div>
<div class="view-tab" data-tab="interactive-view">طريقة العرض التفاعلية</div>
</div>
<!-- محتوى طريقة العرض الكلاسيكية -->
<div id="classic-view" class="view-content active">
<div class="result-section split-view">
<!-- عرض النص المصدر بعد التحديد مع ترقيم السطور -->
<div>
<h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-file-alt text-blue-600 ml-2"></i> النص المصدر (مع التعليم)
</h4>
<div id="sourceTextReview" class="bg-blue-50 rounded-xl p-6 min-h-[200px] border-2 border-blue-100 text-comparison"></div>
</div>
<!-- عرض النص الهدف بعد التحديد مع ترقيم السطور -->
<div>
<h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center">
<i class="fas fa-file-alt text-blue-600 ml-2"></i> النص الهدف (مع التعليم)
</h4>
<div id="targetTextReview" class="bg-gray-50 rounded-xl p-6 min-h-[200px] border-2 border-gray-200 text-comparison"></div>
</div>
</div>
</div>
<!-- محتوى طريقة العرض المقسمة -->
<div id="segment-view" class="view-content">
<div id="segmentedComparisonContainer" class="space-y-4">
<!-- سيتم إنشاء المقاطع هنا بواسطة JavaScript -->
</div>
</div>
<!-- محتوى طريقة العرض التفاعلية الجديدة -->
<div id="interactive-view" class="view-content">
<div class="bg-blue-50 rounded-xl p-6 mb-4">
<div class="flex justify-between items-center mb-4">
<h4 class="text-lg font-bold text-blue-800">عرض تفاعلي للاختلافات</h4>
<div class="flex space-x-2">
<button id="prevDiff" class="bg-blue-200 hover:bg-blue-300 text-blue-800 px-3 py-1 rounded-lg disabled:opacity-50">
<i class="fas fa-arrow-right ml-1"></i> السابق
</button>
<button id="nextDiff" class="bg-blue-200 hover:bg-blue-300 text-blue-800 px-3 py-1 rounded-lg disabled:opacity-50">
<i class="fas fa-arrow-left ml-1"></i> التالي
</button>
<span id="diffCounter" class="bg-white px-3 py-1 rounded-lg">0/0</span>
</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>
<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">ملخص الاختلافات</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">توصيات المعالجة</h5>
<div id="diffRecommendations" class="space-y-2 text-green-800">
<!-- سيتم إضافة التوصيات هنا عن طريق JavaScript -->
</div>
</div>
</div>
</div>
</div>
<!-- صندوق عرض الشرح التفصيلي للنتائج -->
<div id="explanationBox" class="bg-white-classic rounded-2xl shadow-lg p-8 border border-gray-100 hover:shadow-xl 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-info-circle text-blue-600 ml-2"></i> شرح الاختلافات
</h2>
<div id="explanationText" class="text-lg text-gray-700"></div>
</div>
<!-- قسم المسودة (تقسيمات الفقرات) - محسن ومخفي بشكل افتراضي -->
<div id="fullTextDraftSection" class="bg-white-classic rounded-2xl shadow-lg p-8 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">
عرض/إخفاء مسودة التحليل
</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">
تنزيل التقرير (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">
تنزيل التقرير (Word)
</button>
</div>
</div>
</main>
</div>
<!-- ================================
جافا سكريبت: الوظائف والمعالجة
================================= -->
<script>
// تعريف متغيرات PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
// إعدادات API
const DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions';
const DEEPSEEK_API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01';
// إعدادات API ChatGPT-4o
const CHATGPT_API_URL = 'https://api.openai.com/v1/chat/completions';
const CHATGPT_API_KEY = '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';
// إعدادات API4AI OCR
const RAPIDAPI_KEY = 'eb11693cddmshb8bd157e05b74acp1f6aa4jsn4369fa546e55';
const OCR_API_URL = 'https://ocr43.p.rapidapi.com/v1/results';
// متغيرات عامة للتحليل
let fullAnalysisText = ''; // متغير لتخزين النص الكامل للتحليل
let analysisSegments = []; // متغير لتخزين مقاطع التحليل المقسمة
let allDifferences = []; // متغير لتخزين جميع الاختلافات للعرض التفاعلي
let currentDiffIndex = -1; // مؤشر للاختلاف الحالي
// متغيرات OCR
let ocrPagesCount = 0; // عداد لعدد الصفحات المعالجة
let documentPages = []; // مصفوفة لتخزين صفحات المستند
let selectedPages = []; // مصفوفة لتخزين الصفحات المحددة
let extractedTexts = []; // مصفوفة لتخزين النصوص المستخرجة
let extractedPageNumbers = []; // مصفوفة لتخزين أرقام الصفحات المستخرجة
let currentProcessingMode = ''; // لتحديد إذا كانت المعالجة للمصدر أو الهدف
// متغيرات محرر الصور
let canvas = null;
let fabricCanvas = null;
let originalImageData = null;
let isInCropMode = false;
let cropRect = null;
let currentPageIndex = 0;
/*
البرومبت المحسن: يتم فيه تحديد أن النصوص مليئة بالأخطاء والنواقص،
مع معالجة أفضل للأرقام بمختلف لغاتها وأشكالها
*/
const ANALYSIS_PROMPT = `أنت خبير لغوي متخصص في مراجعة الترجمة التقنية. مهمتك مقارنة النص المصدر والنص الهدف بدقة عالية مع العلم أن النصوص مليانة بالأخطاء والنواقص.
لا تقم بإزالة أو تعديل العلامات التالية:
• الأرقام: تحافظ على علامات < و >.
• النصوص المفقودة: تحافظ على علامات __ و __.
• اختلافات المعنى: تحافظ على علامات [MEANING] و [/MEANING].
معالجة الأرقام:
1. اعتبر الأرقام بمختلف أنظمتها (العربية، الهندية، الإنجليزية) متطابقة إذا كانت تمثل نفس القيمة.
2. مثال: ٣ و 3 و ۳ كلها تمثل نفس الرقم، لا تعتبرها خطأ.
3. حدد فقط الأرقام المختلفة في القيمة الفعلية.
اعتمد النص المصدر كأساس للمقارنة، وقم بتحديد:
1. اختلافات الأرقام باستخدام <الرقم_في_المصدر> → <الرقم_في_الهدف>.
2. النصوص المفقودة كما هي بين علامتي __.
3. اختلافات المعنى باستخدام [MEANING] مع الحفاظ على التعليم.
احرص على استخراج الجداول والتنسيقات الخاصة بشكل دقيق.
النص المصدر:
{source}
النص الهدف:
{target}`;
// برومبت OCR محسن للتعامل مع الجداول واستخراج النص بشكل أفضل
const OCR_PROMPT = `قم بمعالجة هذه الصورة بدقة عالية واستخراج كل النص الموجود فيها.
احرص على:
1. استخراج جميع النصوص بدقة عالية، حتى الصغيرة منها أو الباهتة.
2. الحفاظ على تنسيق الفقرات والتباعد بين الأسطر.
3. التمييز بين الجداول والنصوص العادية.
4. استخراج محتوى الجداول بشكل منظم مع الحفاظ على صفوفها وأعمدتها.
5. تحديد النصوص غير المؤكدة بين علامتي [?...?].
قم بتقديم النتائج على النحو التالي:
1. أولاً: النص الكامل المستخرج بتنسيقه الأصلي.
2. ثانياً: إذا وجدت جداول، قدم كل جدول بشكل منفصل مع وصف بنيته.
الصورة للمعالجة:
{image_data}`;
// برومبت لتقسيم النص باستخدام ChatGPT-4o
const TEXT_DIVISION_PROMPT = `قم بتقسيم النص التالي إلى أجزاء منطقية للتحليل.
يجب أن يكون كل جزء وحدة متماسكة (فقرة، قسم، إلخ) لا تتجاوز 500 كلمة.
أعد النص كمصفوفة JSON بالأجزاء. الصيغة: {"segments": ["جزء1", "جزء2", ...]}
النص للتقسيم:
{text}`;
// برومبت لإيجاد الكلمة الأخيرة المشتركة للتزامن
const SYNC_WORD_PROMPT = `قم بتحليل النص التالي وإيجاد آخر كلمة أو جملة قصيرة (لا تزيد عن 3 كلمات) مناسبة ليمكن استخدامها كنقطة تزامن بين الفقرات. اختر كلمة أو جملة تظهر بشكل طبيعي في النص وتكون مناسبة للتقسيم.
أعِد فقط الكلمة أو الجملة المحددة دون أي إضافات أخرى.
النص:
{text}`;
/* =====================================
تهيئة الصفحة وتحميل الإعدادات المحفوظة
===================================== */
document.addEventListener('DOMContentLoaded', function() {
// استرداد عداد OCR وتاريخ آخر معالجة من localStorage
const savedCount = localStorage.getItem('ocrPagesCount');
const lastDate = localStorage.getItem('lastOcrDate');
if (savedCount) {
ocrPagesCount = parseInt(savedCount);
document.getElementById('ocrCounter').textContent = ocrPagesCount;
}
if (lastDate) {
document.getElementById('lastOcrDate').textContent = lastDate;
}
// إضافة أحداث النقر لأزرار معالجة OCR
document.getElementById('processSourceBtn').addEventListener('click', function() {
processFileForOCR('source');
});
document.getElementById('processTargetBtn').addEventListener('click', function() {
processFileForOCR('target');
});
// إضافة أحداث لأزرار عرض PDF
document.getElementById('selectAllBtn')?.addEventListener('click', selectAllPages);
document.getElementById('deselectAllBtn')?.addEventListener('click', deselectAllPages);
document.getElementById('extractTextBtn')?.addEventListener('click', extractText);
// إضافة أحداث لأزرار النتائج
document.getElementById('copyTextBtn')?.addEventListener('click', copyText);
document.getElementById('downloadTextBtn')?.addEventListener('click', downloadText);
document.getElementById('useAsSourceBtn')?.addEventListener('click', function() {
useOcrText('source');
});
document.getElementById('useAsTargetBtn')?.addEventListener('click', function() {
useOcrText('target');
});
// إضافة أحداث لأزرار تحرير الصور
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);
});
/* =====================================
دالة توحيد الأرقام بمختلف أشكالها
===================================== */
function normalizeNumbers(text) {
// تحويل الأرقام العربية والهندية إلى أرقام إنجليزية
return text.replace(/[٠١٢٣٤٥٦٧٨٩]/g, d => d.charCodeAt(0) - 1632) // أرقام عربية
.replace(/[۰۱۲۳۴۵۶۷۸۹]/g, d => d.charCodeAt(0) - 1776); // أرقام هندية
}
/* =====================================
وظائف معالجة OCR
===================================== */
// تحديث عداد OCR
function updateOcrCounter(count) {
ocrPagesCount += count;
document.getElementById('ocrCounter').textContent = ocrPagesCount;
// حفظ العداد في localStorage
localStorage.setItem('ocrPagesCount', ocrPagesCount);
// تحديث تاريخ آخر معالجة
const now = new Date();
const formattedDate = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
document.getElementById('lastOcrDate').textContent = formattedDate;
localStorage.setItem('lastOcrDate', formattedDate);
}
// معالجة الملف للـ OCR
async function processFileForOCR(mode) {
currentProcessingMode = mode;
const fileInput = mode === 'source' ? document.getElementById('sourceFile') : document.getElementById('targetFile');
if (!fileInput.files || fileInput.files.length === 0) {
alert('الرجاء اختيار ملف أولاً');
return;
}
const file = fileInput.files[0];
// إظهار حالة المعالجة
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري معالجة الملف...';
document.getElementById('progressBar').style.width = '0%';
// إخفاء بطاقات أخرى
document.getElementById('pdfPagesCard').classList.add('hidden');
document.getElementById('resultsCard').classList.add('hidden');
// إفراغ مصفوفات الصفحات
documentPages = [];
selectedPages = [];
extractedTexts = [];
extractedPageNumbers = [];
try {
const fileType = file.name.split('.').pop().toLowerCase();
if (fileType === 'pdf') {
await processPdf(file);
} else if (['jpg', 'jpeg', 'png'].includes(fileType)) {
await processImage(file);
} else {
throw new Error('نوع الملف غير مدعوم. يرجى اختيار ملف PDF أو صورة.');
}
// إظهار بطاقة صفحات PDF
document.getElementById('pdfPagesCard').classList.remove('hidden');
// إخفاء حالة المعالجة
document.getElementById('processingStatus').classList.add('hidden');
} catch (error) {
console.error('Error processing file:', error);
document.getElementById('statusText').textContent = `خطأ: ${error.message}`;
}
}
// معالجة ملف PDF
async function processPdf(file) {
try {
// تحميل ملف PDF
const arrayBuffer = await file.arrayBuffer();
const pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
// التحقق من عدد الصفحات
const numPages = pdfDoc.numPages;
if (numPages > 100) {
alert('هذا الملف يحتوي على أكثر من 100 صفحة. سيتم معالجة أول 100 صفحة فقط.');
}
// مسح حاوية الصفحات
const pdfPagesContainer = document.getElementById('pdfPagesContainer');
pdfPagesContainer.innerHTML = '';
// عرض أول 100 صفحة كصور مصغرة
const maxPages = Math.min(numPages, 100);
for (let i = 1; i <= maxPages; i++) {
// تحديث شريط التقدم
document.getElementById('progressBar').style.width = `${(i / maxPages) * 100}%`;
document.getElementById('statusText').textContent = `جاري معالجة الصفحة ${i} من ${maxPages}...`;
// تحويل الصفحة إلى صورة
const pageImage = await convertPdfPageToImage(pdfDoc, i);
// إضافة الصفحة إلى المصفوفة
documentPages.push({
pageNumber: i,
imageData: pageImage.imageData,
width: pageImage.width,
height: pageImage.height
});
// إنشاء عنصر للصفحة في واجهة المستخدم
createPageElement(pageImage.imageData, i);
}
} catch (error) {
console.error('Error processing PDF:', error);
throw error;
}
}
// معالجة ملف صورة
async function processImage(file) {
try {
// قراءة الصورة كـ Data URL
const imageData = await readFileAsDataURL(file);
// إنشاء صورة لقياس أبعادها
const img = new Image();
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = imageData;
});
// مسح حاوية الصفحات
const pdfPagesContainer = document.getElementById('pdfPagesContainer');
pdfPagesContainer.innerHTML = '';
// إضافة الصورة إلى المصفوفة
documentPages = [{
pageNumber: 1,
imageData: imageData,
width: img.width,
height: img.height
}];
// إنشاء عنصر للصورة في واجهة المستخدم
createPageElement(imageData, 1);
// تحديث شريط التقدم
document.getElementById('progressBar').style.width = '100%';
document.getElementById('statusText').textContent = 'تمت معالجة الصورة بنجاح';
} catch (error) {
console.error('Error processing image:', error);
throw error;
}
}
// تحويل صفحة PDF إلى صورة
async function convertPdfPageToImage(pdfDoc, pageNumber, scale = 1.5) {
try {
// الحصول على الصفحة من مستند PDF
const page = await pdfDoc.getPage(pageNumber);
// إنشاء عنصر canvas لرسم الصفحة
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// ضبط حجم الـ canvas بناءً على حجم الصفحة ومقياس التكبير
const viewport = page.getViewport({ scale });
canvas.width = viewport.width;
canvas.height = viewport.height;
// رسم الصفحة على الـ canvas
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
// تحويل الـ canvas إلى صورة بصيغة PNG
return {
imageData: canvas.toDataURL('image/png'),
width: viewport.width,
height: viewport.height,
pageNumber: pageNumber
};
} catch (error) {
console.error(`Error converting PDF page ${pageNumber} to image:`, error);
throw error;
}
}
// إنشاء عنصر للصفحة في واجهة المستخدم
function createPageElement(imageData, pageNumber) {
const pageDiv = document.createElement('div');
pageDiv.className = 'pdf-page';
pageDiv.dataset.page = pageNumber;
const img = document.createElement('img');
img.src = imageData;
img.alt = `صفحة ${pageNumber}`;
const pageNumberDiv = document.createElement('div');
pageNumberDiv.className = 'page-number';
pageNumberDiv.textContent = `صفحة ${pageNumber}`;
pageDiv.appendChild(img);
pageDiv.appendChild(pageNumberDiv);
// إضافة حدث النقر للتحديد
pageDiv.addEventListener('click', function() {
// تحديد/إلغاء تحديد هذه الصفحة
this.classList.toggle('selected');
// تحديث مصفوفة الصفحات المحددة
const page = parseInt(this.dataset.page);
if (this.classList.contains('selected')) {
if (!selectedPages.includes(page)) {
selectedPages.push(page);
}
} else {
const index = selectedPages.indexOf(page);
if (index > -1) {
selectedPages.splice(index, 1);
}
}
// عرض الصفحة المحددة في محرر الصور
if (this.classList.contains('selected') && documentPages.length > 0) {
const selectedPage = documentPages.find(p => p.pageNumber === page);
if (selectedPage) {
displayImageForEditing(selectedPage.imageData, page);
}
}
});
document.getElementById('pdfPagesContainer').appendChild(pageDiv);
}
// عرض الصورة للتحرير
function displayImageForEditing(imageData, pageNumber) {
currentPageIndex = pageNumber;
// إظهار محرر الصور
document.getElementById('imageEditor').classList.remove('hidden');
// إنشاء صورة جديدة لتحميلها
const img = new Image();
img.onload = function() {
// إعداد الكانفاس
canvas = document.getElementById('imageCanvas');
canvas.width = img.width;
canvas.height = img.height;
const context = canvas.getContext('2d');
// رسم الصورة على الكانفاس
context.drawImage(img, 0, 0, img.width, img.height);
// تخزين بيانات الصورة الأصلية للاستعادة إذا لزم الأمر
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
// إنشاء محرر الصور باستخدام fabric.js
initFabricCanvas();
};
img.src = imageData;
}
// إنشاء محرر الصور باستخدام fabric.js
function initFabricCanvas() {
// إذا كان هناك محرر سابق، قم بإزالته
if (fabricCanvas) {
fabricCanvas.dispose();
}
// إنشاء محرر جديد
fabricCanvas = new fabric.Canvas('imageCanvas', {
selection: false
});
// إضافة الصورة من الكانفاس
fabric.Image.fromURL(canvas.toDataURL(), function(img) {
img.selectable = false;
fabricCanvas.add(img);
fabricCanvas.renderAll();
});
// إعادة ضبط وضع القص
isInCropMode = false;
if (cropRect) {
fabricCanvas.remove(cropRect);
cropRect = null;
}
}
// تدوير الصورة لليسار
function rotateImageLeft() {
if (!fabricCanvas) return;
const objects = fabricCanvas.getObjects();
if (objects.length === 0) return;
const img = objects[0];
img.rotate((img.angle || 0) - 90);
fabricCanvas.renderAll();
}
// تدوير الصورة لليمين
function rotateImageRight() {
if (!fabricCanvas) return;
const objects = fabricCanvas.getObjects();
if (objects.length === 0) return;
const img = objects[0];
img.rotate((img.angle || 0) + 90);
fabricCanvas.renderAll();
}
// قلب الصورة أفقيًا
function flipImageHorizontal() {
if (!fabricCanvas) return;
const objects = fabricCanvas.getObjects();
if (objects.length === 0) return;
const img = objects[0];
img.set('flipX', !img.flipX);
fabricCanvas.renderAll();
}
// قلب الصورة عموديًا
function flipImageVertical() {
if (!fabricCanvas) return;
const objects = fabricCanvas.getObjects();
if (objects.length === 0) return;
const img = objects[0];
img.set('flipY', !img.flipY);
fabricCanvas.renderAll();
}
// تنشيط وضع قص الصورة
function activateCropMode() {
if (!fabricCanvas) return;
// إلغاء وضع القص إذا كان نشطًا بالفعل
if (isInCropMode) {
if (cropRect) {
fabricCanvas.remove(cropRect);
cropRect = null;
}
isInCropMode = false;
return;
}
// إنشاء مستطيل للقص
const canvasWidth = fabricCanvas.getWidth();
const canvasHeight = fabricCanvas.getHeight();
cropRect = new fabric.Rect({
left: canvasWidth * 0.2,
top: canvasHeight * 0.2,
width: canvasWidth * 0.6,
height: canvasHeight * 0.6,
fill: 'rgba(0,0,0,0.1)',
stroke: 'rgba(59, 130, 246, 0.9)',
strokeWidth: 2,
strokeDashArray: [5, 5],
selectable: true,
hasRotatingPoint: false
});
fabricCanvas.add(cropRect);
fabricCanvas.setActiveObject(cropRect);
isInCropMode = true;
}
// تطبيق القص على الصورة
function applyCrop() {
if (!fabricCanvas || !cropRect) return;
const objects = fabricCanvas.getObjects();
if (objects.length < 2) return; // لابد من وجود الصورة ومستطيل القص
const img = objects[0];
// احصل على الإحداثيات النسبية لمنطقة القص
const imgWidth = img.getScaledWidth();
const imgHeight = img.getScaledHeight();
const cropX = cropRect.left - img.left;
const cropY = cropRect.top - img.top;
const cropWidth = cropRect.getScaledWidth();
const cropHeight = cropRect.getScaledHeight();
// التحقق من أن منطقة القص ضمن حدود الصورة
if (cropX < 0 || cropY < 0 || cropX + cropWidth > imgWidth || cropY + cropHeight > imgHeight) {
alert('منطقة القص خارج حدود الصورة');
return;
}
// إنشاء كانفاس مؤقت للقص
const tempCanvas = document.createElement('canvas');
tempCanvas.width = cropWidth;
tempCanvas.height = cropHeight;
const tempCtx = tempCanvas.getContext('2d');
// رسم الجزء المقصوص فقط
const originalCanvas = document.getElementById('imageCanvas');
tempCtx.drawImage(
originalCanvas,
cropX, cropY, cropWidth, cropHeight,
0, 0, cropWidth, cropHeight
);
// إعادة تهيئة الكانفاس بالصورة المقصوصة
fabricCanvas.clear();
fabric.Image.fromURL(tempCanvas.toDataURL(), function(croppedImg) {
croppedImg.selectable = false;
fabricCanvas.add(croppedImg);
fabricCanvas.renderAll();
});
// إلغاء وضع القص
isInCropMode = false;
cropRect = null;
}
// إعادة ضبط الصورة إلى حالتها الأصلية
function resetImage() {
if (!fabricCanvas || !originalImageData) return;
// إعادة الصورة الأصلية
const originalCanvas = document.getElementById('imageCanvas');
const ctx = originalCanvas.getContext('2d');
ctx.putImageData(originalImageData, 0, 0);
// إعادة تهيئة محرر الصور
initFabricCanvas();
}
// تحسين تباين الصورة
function improveContrast() {
if (!fabricCanvas) return;
const originalCanvas = document.getElementById('imageCanvas');
const ctx = originalCanvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, originalCanvas.width, originalCanvas.height);
const data = imageData.data;
// تحديد الحد الأدنى والأقصى للقيم
let min = 255;
let max = 0;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const value = (r + g + b) / 3;
if (value < min) min = value;
if (value > max) max = value;
}
// تطبيق ضبط التباين
const contrast = 1.5; // قيمة التباين: أكبر من 1 لزيادة التباين
const brightness = 20; // قيمة السطوع: موجب لزيادة السطوع
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.max(0, Math.min(255, ((data[i] - 128) * contrast) + 128 + brightness));
data[i + 1] = Math.max(0, Math.min(255, ((data[i + 1] - 128) * contrast) + 128 + brightness));
data[i + 2] = Math.max(0, Math.min(255, ((data[i + 2] - 128) * contrast) + 128 + brightness));
}
// تحديث الصورة
ctx.putImageData(imageData, 0, 0);
// إعادة تهيئة محرر الصور
initFabricCanvas();
}
// تحديد كل الصفحات
function selectAllPages() {
document.querySelectorAll('.pdf-page').forEach(page => {
page.classList.add('selected');
const pageNumber = parseInt(page.dataset.page);
if (!selectedPages.includes(pageNumber)) {
selectedPages.push(pageNumber);
}
});
}
// إلغاء تحديد كل الصفحات
function deselectAllPages() {
document.querySelectorAll('.pdf-page').forEach(page => {
page.classList.remove('selected');
});
selectedPages = [];
}
// استخراج النص من الصفحات المحددة
async function extractText() {
if (documentPages.length === 0) {
alert('لا توجد صفحات للمعالجة');
return;
}
// اختيار الصفحات المحددة فقط أو كل الصفحات إذا لم يتم تحديد أي صفحة
const pagesToProcess = selectedPages.length > 0
? documentPages.filter(page => selectedPages.includes(page.pageNumber))
: documentPages;
if (pagesToProcess.length === 0) {
alert('الرجاء تحديد صفحة واحدة على الأقل');
return;
}
// إظهار حالة المعالجة
document.getElementById('processingStatus').classList.remove('hidden');
document.getElementById('statusText').textContent = 'جاري استخراج النص...';
document.getElementById('progressBar').style.width = '0%';
try {
extractedTexts = [];
extractedPageNumbers = [];
// معالجة كل صفحة
for (let i = 0; i < pagesToProcess.length; i++) {
const page = pagesToProcess[i];
// تحديث التقدم
document.getElementById('progressBar').style.width = `${((i + 1) / pagesToProcess.length) * 100}%`;
document.getElementById('statusText').textContent = `جاري معالجة الصفحة ${i + 1} من ${pagesToProcess.length}...`;
// إذا كانت هناك تعديلات على الصورة، نحصل على الصورة المحررة من fabric.js
let imageData = page.imageData;
if (fabricCanvas && currentPageIndex === page.pageNumber) {
imageData = document.getElementById('imageCanvas').toDataURL('image/jpeg');
}
// استخراج النص من الصورة
const pageText = await extractTextFromImage(imageData, page.pageNumber);
extractedTexts.push(pageText);
extractedPageNumbers.push(page.pageNumber);
}
// تحديث عداد OCR
updateOcrCounter(pagesToProcess.length);
// جمع النصوص المستخرجة
const combinedText = extractedTexts.join('\n\n');
// عرض النص المستخرج
document.getElementById('resultText').textContent = combinedText;
// عرض معاينة مرئية منظمة للصفحات
generateResultPreview();
document.getElementById('resultsCard').classList.remove('hidden');
// إخفاء حالة المعالجة
document.getElementById('processingStatus').classList.add('hidden');
} catch (error) {
console.error('Error extracting text:', error);
document.getElementById('statusText').textContent = `خطأ: ${error.message}`;
}
}
// إنشاء معاينة منظمة للنتائج
function generateResultPreview() {
const resultPreview = document.getElementById('resultPreview');
resultPreview.innerHTML = '';
if (extractedTexts.length === 0) {
return;
}
// عرض أول ثلاث صفحات كمعاينة
const previewPages = Math.min(extractedTexts.length, 3);
for (let i = 0; i < previewPages; i++) {
const pageText = extractedTexts[i];
const pageNumber = extractedPageNumbers[i];
// إنشاء معاينة للصفحة
const previewDiv = document.createElement('div');
previewDiv.className = 'page-preview';
const pageTitle = document.createElement('h4');
pageTitle.textContent = `صفحة ${pageNumber}`;
const textPreview = document.createElement('p');
// عرض أول 100 حرف من كل صفحة
const textContent = pageText.replace(`=== صفحة ${pageNumber} ===\n`, '');
textPreview.textContent = textContent.length > 100 ?
textContent.substring(0, 100) + '...' :
textContent;
previewDiv.appendChild(pageTitle);
previewDiv.appendChild(textPreview);
resultPreview.appendChild(previewDiv);
}
// إضافة رسالة إذا كان هناك المزيد من الصفحات
if (extractedTexts.length > 3) {
const morePages = document.createElement('p');
morePages.className = 'text-gray-500 text-center';
morePages.textContent = `+ ${extractedTexts.length - 3} صفحات أخرى...`;
resultPreview.appendChild(morePages);
}
}
// استخراج النص من صورة باستخدام API
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, `page_${pageNumber}.png`);
// طلب 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 {
// استخراج النص من الاستجابة مع الحفاظ على تنسيق الصفوف
const text = data.results[0].entities[0].objects[0].entities[0].text;
return `=== صفحة ${pageNumber} ===\n${text}`;
} catch (e) {
console.error('Error parsing OCR response:', e);
return `=== صفحة ${pageNumber} ===\n[خطأ في معالجة النص]`;
}
} catch (error) {
console.error(`Error in OCR for page ${pageNumber}:`, error);
throw error;
}
}
// نسخ النص المستخرج
function copyText() {
const resultText = document.getElementById('resultText');
const text = resultText.textContent;
if (!text || text === 'لم يتم استخراج نص بعد.') {
alert('لا يوجد نص للنسخ');
return;
}
navigator.clipboard.writeText(text)
.then(() => {
const copyBtn = document.getElementById('copyTextBtn');
copyBtn.innerHTML = '<i class="fas fa-check ml-1"></i> تم النسخ';
setTimeout(() => {
copyBtn.innerHTML = '<i class="far fa-copy ml-1"></i> نسخ النص';
}, 2000);
})
.catch(err => alert('حدث خطأ أثناء نسخ النص: ' + err));
}
// تنزيل النص
function downloadText() {
const text = document.getElementById('resultText').textContent;
if (!text || text === 'لم يتم استخراج نص بعد.') {
alert('لا يوجد نص للتنزيل');
return;
}
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'alrihan_ocr_text.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// استخدام النص المستخرج في النص المصدر أو الهدف
function useOcrText(targetField) {
const resultText = document.getElementById('resultText');
const text = resultText.textContent;
if (!text || text === 'لم يتم استخراج نص بعد.') {
alert('لا يوجد نص للاستخدام');
return;
}
// تنظيف النص من علامات الصفحات
const cleanText = text.replace(/=== صفحة \d+ ===\n/g, '');
if (targetField === 'source') {
document.getElementById('sourceText').value = cleanText;
addError('تم نقل النص المستخرج إلى حقل النص المصدر', 'info');
} else {
document.getElementById('targetText').value = cleanText;
addError('تم نقل النص المستخرج إلى حقل النص الهدف', 'info');
}
}
// قراءة ملف كـ Data URL
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => resolve(e.target.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
/* =====================================
دالة لتقسيم النص باستخدام ChatGPT-4o
===================================== */
async function divideTextWithChatGPT(text) {
try {
// إذا كان النص قصيرًا، لا داعي لتقسيمه
if (countWords(text) <= 500) {
return [text];
}
// تحضير البيانات لـ ChatGPT-4o
const prompt = TEXT_DIVISION_PROMPT.replace("{text}", text);
const payload = {
model: "gpt-4o",
messages: [
{ role: "system", content: "أنت مساعد متخصص في تقسيم النصوص إلى أجزاء منطقية." },
{ role: "user", content: prompt }
],
temperature: 0.3,
max_tokens: 1024
};
// استدعاء API ChatGPT
const response = await fetch(CHATGPT_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + CHATGPT_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('خطأ في API ChatGPT: ' + response.statusText);
}
const data = await response.json();
const content = data.choices[0].message.content.trim();
// محاولة تحليل الاستجابة كـ JSON
try {
const jsonResponse = JSON.parse(content);
if (jsonResponse.segments && Array.isArray(jsonResponse.segments)) {
return jsonResponse.segments;
}
} catch (e) {
// إذا تعذر تحليلها كـ JSON، حاول استخراج الأجزاء يدويًا
const segments = content.split(/\n{2,}/).filter(seg => seg.trim().length > 0);
if (segments.length > 0) {
return segments;
}
}
// إذا فشل كل شيء، قسم النص إلى فقرات
return text.split(/\n{2,}/).filter(para => para.trim().length > 0);
} catch (error) {
console.error('خطأ في تقسيم النص باستخدام ChatGPT:', error);
// الخطة البديلة: تقسيم حسب الفقرات
return text.split(/\n{2,}/).filter(para => para.trim().length > 0);
}
}
/* =====================================
دالة لإيجاد الكلمة المتزامنة في نهاية كل فقرة
===================================== */
async function findSyncWord(text) {
try {
// تحضير البيانات لـ ChatGPT-4o
const prompt = SYNC_WORD_PROMPT.replace("{text}", text);
const payload = {
model: "gpt-4o",
messages: [
{ role: "system", content: "أنت مساعد متخصص في تحليل النصوص وإيجاد نقاط تزامن مناسبة." },
{ role: "user", content: prompt }
],
temperature: 0.3,
max_tokens: 50
};
// استدعاء API ChatGPT
const response = await fetch(CHATGPT_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + CHATGPT_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('خطأ في API ChatGPT: ' + response.statusText);
}
const data = await response.json();
const syncWord = data.choices[0].message.content.trim();
return syncWord;
} catch (error) {
console.error('خطأ في إيجاد الكلمة المتزامنة:', error);
// في حالة الفشل، نعيد كلمة افتراضية
return "نهاية الفقرة";
}
}
/* =====================================
دالة لتحليل الأجزاء باستخدام DeepSeek ودمج النتائج
===================================== */
async function analyzeTextSegments(sourceSegments, targetSegments) {
// التحقق من أن لدينا نفس عدد الأجزاء
if (sourceSegments.length !== targetSegments.length) {
throw new Error('عدد أجزاء المصدر والهدف غير متطابق');
}
let combinedAnalysis = '';
const progressDiv = document.createElement('div');
progressDiv.className = "bg-blue-100 p-4 rounded-xl mb-4";
progressDiv.innerHTML = `<div class="flex items-center justify-between">
<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>جارٍ التحليل...</span>
</div>
<span id="segmentProgress">0/${sourceSegments.length}</span>
</div>`;
document.getElementById('errorsList').appendChild(progressDiv);
// تخزين مقاطع التحليل المنفصلة
analysisSegments = [];
// تحليل كل زوج من الأجزاء
for (let i = 0; i < sourceSegments.length; i++) {
// تحديث التقدم
document.getElementById('segmentProgress').textContent = `${i+1}/${sourceSegments.length}`;
// بناء البرومبت لهذا الجزء
const prompt = ANALYSIS_PROMPT
.replace("{source}", sourceSegments[i])
.replace("{target}", targetSegments[i]);
// استدعاء DeepSeek API
const payload = {
model: "deepseek-reasoner",
messages: [
{ role: "system", content: "أنت خبير في تحليل النصوص ومقارنتها بدقة عالية." },
{ role: "user", content: prompt }
],
temperature: 0.3,
max_tokens: 2048,
stream: false
};
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error('حدث خطأ بالشبكة: ' + response.statusText);
}
const data = await response.json();
const segmentAnalysis = data.choices[0].message.content.trim();
// أضف هذا المقطع إلى المصفوفة
analysisSegments.push({
source: sourceSegments[i],
target: targetSegments[i],
analysis: segmentAnalysis
});
// دمج مع التحليل السابق
if (i > 0) combinedAnalysis += '\n\n---\n\n';
combinedAnalysis += `[الجزء ${i+1}]\n${segmentAnalysis}`;
}
progressDiv.remove();
return combinedAnalysis;
}
/* =====================================
دالة عرض التقسيمات كمسودة قابلة للطي
===================================== */
async function displayDraftSections() {
const container = document.getElementById('paragraphDivisionsContainer');
container.innerHTML = ''; // مسح المحتوى الحالي
// إنشاء قسم لكل مقطع تحليل
for (let i = 0; i < analysisSegments.length; i++) {
const segment = analysisSegments[i];
// البحث عن كلمة متزامنة في نهاية المقطع
const syncWord = await findSyncWord(segment.source);
// إنشاء عنصر القسم القابل للطي
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>القسم ${i+1}: ${truncateText(segment.source, 50)}</span>
</div>
<i class="fas fa-chevron-down"></i>
`;
// إنشاء محتوى القسم
const contentDiv = document.createElement('div');
contentDiv.className = 'section-content';
// تحديد النص المصدر مع تمييز الكلمة المتزامنة في النهاية
const sourceWithSync = highlightSyncWord(segment.source, syncWord);
// تحديد النص الهدف مع تمييز الكلمة المتزامنة في النهاية إن وجدت
const targetWithSync = highlightSyncWord(segment.target, syncWord);
contentDiv.innerHTML = `
<div class="paragraph-section mb-4">
<h4 class="font-bold text-gray-700 mb-2">النص المصدر:</h4>
<div class="bg-blue-50 p-3 rounded-lg">${sourceWithSync}</div>
</div>
<div class="paragraph-section mb-4">
<h4 class="font-bold text-gray-700 mb-2">النص الهدف:</h4>
<div class="bg-blue-50 p-3 rounded-lg">${targetWithSync}</div>
</div>
<div class="paragraph-section">
<h4 class="font-bold text-gray-700 mb-2">التحليل:</h4>
<div class="bg-gray-50 p-3 rounded-lg">${formatAnalysisText(segment.analysis)}</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');
});
}
}
/* =====================================
دالة عرض المقاطع في طريقة العرض المقسمة
===================================== */
function displaySegmentedView() {
const container = document.getElementById('segmentedComparisonContainer');
container.innerHTML = ''; // مسح المحتوى الحالي
// إذا لم تكن هناك مقاطع، أظهر رسالة
if (!analysisSegments || analysisSegments.length === 0) {
container.innerHTML = '<div class="p-4 bg-yellow-50 rounded-lg text-center">لم يتم تحليل أي مقاطع بعد. قم بتحليل النصوص أولاً.</div>';
return;
}
// إنشاء عنصر لكل مقطع
for (let i = 0; i < analysisSegments.length; i++) {
const segment = analysisSegments[i];
// تحليل المقطع لاستخراج الأخطاء
const hasNumbers = segment.analysis.includes('<') && segment.analysis.includes('>');
const hasMissing = segment.analysis.includes('__');
const hasMeaning = segment.analysis.includes('[MEANING]');
// إنشاء علامات الأخطاء
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" style="background-color: #d1fae5; color: #065f46;">مطابق</span>';
}
// إنشاء عنصر المقطع
const segmentDiv = document.createElement('div');
segmentDiv.className = 'segment-comparison';
// تطبيق التحديد على النصوص
const sourceHighlighted = applyHighlights(segment.source, segment.analysis);
const targetHighlighted = applyHighlights(segment.target, segment.analysis);
segmentDiv.innerHTML = `
<div class="segment-header">
<div>المقطع ${i+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">
<strong>ملاحظات:</strong> ${formatAnalysisText(segment.analysis)}
</div>
`;
container.appendChild(segmentDiv);
}
}
/* =====================================
دالة إعداد وعرض العرض التفاعلي
===================================== */
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], 10)
});
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], 10)
});
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], 10)
});
meaningDiffCount++;
});
});
// تحديث عدادات الاختلافات
document.getElementById('numberDiffCount').textContent = numberDiffCount;
document.getElementById('missingTextCount').textContent = missingTextCount;
document.getElementById('meaningDiffCount').textContent = meaningDiffCount;
// تنشيط/تعطيل أزرار التنقل
updateNavigationButtons();
// إعداد توصيات المعالجة
const recommendationsContainer = document.getElementById('diffRecommendations');
recommendationsContainer.innerHTML = '';
if (allDifferences.length === 0) {
recommendationsContainer.innerHTML = '<p>لا توجد اختلافات تحتاج إلى معالجة!</p>';
} else {
// إضافة توصيات مخصصة بناءً على أنواع الاختلافات
if (numberDiffCount > 0) {
recommendationsContainer.innerHTML += `<p>• راجع الأرقام واحرص على تطابقها بين النصين.</p>`;
}
if (missingTextCount > 0) {
recommendationsContainer.innerHTML += `<p>• أضف النصوص المفقودة في الترجمة لضمان اكتمال المحتوى.</p>`;
}
if (meaningDiffCount > 0) {
recommendationsContainer.innerHTML += `<p>• صحح اختلافات المعنى لضمان دقة الترجمة.</p>`;
}
// توصية عامة
recommendationsContainer.innerHTML += `<p>• استخدم وضع العرض المقسم للتعديل الدقيق.</p>`;
}
// تعيين أحداث النقر لأزرار التنقل
document.getElementById('prevDiff').addEventListener('click', showPreviousDifference);
document.getElementById('nextDiff').addEventListener('click', showNextDifference);
// عرض عداد الاختلافات
document.getElementById('diffCounter').textContent = allDifferences.length > 0 ?
`0/${allDifferences.length}` : "0/0";
}
// الحصول على السياق المحيط بنص معين
function getContextAroundMatch(text, match, contextSize) {
const index = text.indexOf(match);
if (index === -1) return "";
const start = Math.max(0, index - contextSize);
const end = Math.min(text.length, index + match.length + contextSize);
let context = text.substring(start, end);
if (start > 0) context = '...' + context;
if (end < text.length) context = context + '...';
return context;
}
// عرض الاختلاف التالي
function showNextDifference() {
if (allDifferences.length === 0) return;
currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length;
displayCurrentDifference();
updateNavigationButtons();
}
// عرض الاختلاف السابق
function showPreviousDifference() {
if (allDifferences.length === 0) return;
currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % allDifferences.length;
displayCurrentDifference();
updateNavigationButtons();
}
// عرض الاختلاف الحالي
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";
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;
highlightedContext = highlightedContext.replace(new RegExp(escapeRegExp(diff.text), 'g'),
`<mark class="${diff.type === 'number' ? 'highlight-number' : diff.type === 'missing' ? 'highlight-missing' : 'highlight-meaning'}">${diff.text}</mark>`);
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">الاختلاف:</div>
<div class="bg-gray-50 p-3 rounded-lg mb-3">${highlightedContext}</div>
<div class="text-sm text-gray-600">
<div class="font-bold mb-1">التوصية:</div>
${getRecommendationForDiff(diff)}
</div>
`;
document.getElementById('diffCounter').textContent = `${currentDiffIndex + 1}/${allDifferences.length}`;
}
// الحصول على توصية مخصصة لكل نوع اختلاف
function getRecommendationForDiff(diff) {
if (diff.type === 'number') {
return 'تأكد من صحة الرقم في النص الهدف وتطابقه مع النص المصدر.';
} else if (diff.type === 'missing') {
return 'أضف النص المفقود إلى الترجمة للحفاظ على اكتمال المعنى.';
} else if (diff.type === 'meaning') {
return 'راجع الترجمة للتأكد من نقل المعنى الصحيح دون تحريف.';
}
return '';
}
// تحديث حالة أزرار التنقل
function updateNavigationButtons() {
document.getElementById('prevDiff').disabled = allDifferences.length === 0;
document.getElementById('nextDiff').disabled = allDifferences.length === 0;
}
/* =====================================
دوال مساعدة للتعامل مع النصوص
===================================== */
// حساب عدد الكلمات في النص
function countWords(text) {
return text.trim().split(/\s+/).filter(word => word !== "").length;
}
// هروب أحرف Regex الخاصة
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// تقسيم النص إلى أسطر مع عرض رقم السطر
function splitIntoLines(text) {
return text.split('\n').map((line, i) => `<div class="line-item"><span class="line-number">${i+1}:</span> <span class="line-text">${line}</span></div>`).join('');
}
// الحصول على رقم السطر لظهور عبارة معينة
function getLineNumber(text, substring) {
const index = text.indexOf(substring);
if (index === -1) return "غير محدد";
return text.substring(0, index).split("\n").length;
}
// اقتطاع النص إذا كان طويلا جدا
function truncateText(text, maxLength) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
// تنسيق نص التحليل بتمييز الكلمات المهمة
function formatAnalysisText(text) {
// تمييز الكلمات المهمة مثل "الأرقام" و "المفقودة" و "المعنى"
text = text.replace(/الأرقام/g, '<span class="font-bold text-blue-600">الأرقام</span>');
text = text.replace(/المفقودة/g, '<span class="font-bold text-blue-600">المفقودة</span>');
text = text.replace(/المعنى/g, '<span class="font-bold text-blue-600">المعنى</span>');
// تحديد علامات التمييز
text = text.replace(/<([^<>]+)>/g, '<span class="highlight-number">&#x3C;$1&#x3E;</span>');
text = text.replace(/__(.*?)__/g, '<span class="highlight-missing">__$1__</span>');
text = text.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '<span class="highlight-meaning">$1</span>');
return text;
}
// تمييز الكلمة المتزامنة في النص
function highlightSyncWord(text, syncWord) {
if (!syncWord || !text.includes(syncWord)) return text;
// تجنب تمييز الكلمة إذا كانت جزءًا من كلمة أكبر
const regex = new RegExp(`(\\b${escapeRegExp(syncWord)}\\b)`, 'g');
// جعل الظهور الأخير للكلمة ممَيز
const lastIndex = text.lastIndexOf(syncWord);
if (lastIndex !== -1) {
const beforeSync = text.substring(0, lastIndex);
const afterSync = text.substring(lastIndex + syncWord.length);
return beforeSync + '<span class="sync-word">' + syncWord + '</span>' + afterSync;
}
return text;
}
/* =====================================
دوال التمييز (Highlighting) للنتائج
===================================== */
// دالة محسنة لتحديد النصوص التي بها مشاكل فقط على الأسطر المتأثرة
function applyHighlights(originalText, analysisOutput) {
// استخراج النصوص التي بها مشاكل من التحليل
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 lines = originalText.split('\n');
// معالجة كل سطر على حدة وتطبيق التحديد إذا وُجدت مشكلة
const highlightedLines = lines.map(line => {
// تحديد اختلافات الأرقام مع مراعاة أشكال الأرقام المختلفة
numberMatches.forEach(phrase => {
if (line.includes(phrase)) {
// استخدام المقارنة بعد توحيد الأرقام
const normalizedPhrase = normalizeNumbers(phrase);
const normalizedLine = normalizeNumbers(line);
// تجنب تحديد النص إذا كان الاختلاف فقط في شكل الأرقام
if (normalizedLine.includes(normalizedPhrase)) {
// لا تعامله كاختلاف جوهري
} else {
const regex = new RegExp(escapeRegExp(phrase), 'g');
line = line.replace(regex, `<span class="highlight-number">${phrase}</span>`);
}
}
});
// تحديد النصوص المفقودة
missingMatches.forEach(phrase => {
if (line.includes(phrase)) {
const regex = new RegExp(escapeRegExp(phrase), 'g');
line = line.replace(regex, `<span class="highlight-missing">__${phrase}__</span>`);
}
});
// تحديد اختلافات المعنى
meaningMatches.forEach(phrase => {
if (line.includes(phrase)) {
const regex = new RegExp(escapeRegExp(phrase), 'g');
line = line.replace(regex, `<span class="highlight-meaning">${phrase}</span>`);
}
});
return line;
});
return highlightedLines.join('\n');
}
/* =====================================
دالة توليد الشرح التفصيلي Organized Explanation
يتم تقسيم الشرح إلى خطوات منظمة
===================================== */
function generateExplanation(sourceText, analysisOutput) {
let steps = [];
// الخطوة 1: النصوص المفقودة
const missingRegex = /__(.*?)__/g;
let match;
while ((match = missingRegex.exec(analysisOutput)) !== null) {
const phrase = match[1].trim();
if (phrase) {
const lineNum = getLineNumber(sourceText, phrase);
steps.push(`<li><strong>الخطوة 1:</strong> في السطر ${lineNum}، الجزء "<span class="highlight-missing">__${phrase}__</span>" من النص المصدر مفقود في النص الهدف. تأكد من إضافته لتحسين الدقة.</li>`);
}
}
// الخطوة 2: اختلافات الأرقام
const numberRegex = /<([^<>]+)>/g;
while ((match = numberRegex.exec(analysisOutput)) !== null) {
const phrase = match[1].trim();
if (phrase) {
const lineNum = getLineNumber(sourceText, phrase);
steps.push(`<li><strong>الخطوة 2:</strong> في السطر ${lineNum}، الرقم "<span class="highlight-number">${phrase}</span>" في المصدر لا يتطابق مع الرقم في الهدف. يرجى المراجعة.</li>`);
}
}
// الخطوة 3: اختلافات المعنى
const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g;
while ((match = meaningRegex.exec(analysisOutput)) !== null) {
const phrase = match[1].trim();
if (phrase) {
const lineNum = getLineNumber(sourceText, phrase);
steps.push(`<li><strong>الخطوة 3:</strong> في السطر ${lineNum}، تم العثور على اختلاف في المعنى مع التعبير "<span class="highlight-meaning">${phrase}</span>". تحقق من الدقة.</li>`);
}
}
// إذا لم يكن هناك اختلافات
if (steps.length === 0) {
return `<p>النصوص متطابقة تماماً ولا توجد فروقات تحتاج للتنبيه.</p>`;
}
return `<ol class="list-decimal ml-6 space-y-2">${steps.join('')}</ol>`;
}
/* =====================================
دالة معالجة ملفات (DOCX)
===================================== */
async function processFile(file) {
let text = "";
if (file.type === 'application/pdf') {
try {
// تحميل PDF واستخراج النص
const arrayBuffer = await file.arrayBuffer();
const loadingTask = pdfjsLib.getDocument(arrayBuffer);
const pdf = await loadingTask.promise;
const numPages = pdf.numPages;
let fullText = "";
for (let i = 1; i <= numPages; i++) {
const page = await pdf.getPage(i);
const content = await page.getTextContent();
const pageText = content.items.map(item => item.str).join(' ');
fullText += pageText + "\n\n";
}
text = fullText;
} catch (error) {
console.error('خطأ في استخراج النص من PDF:', error);
throw new Error('فشل استخراج النص من ملف PDF');
}
} else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
const arrayBuffer = await file.arrayBuffer();
const result = await mammoth.extractRawText({ arrayBuffer });
text = result.value;
} else {
throw new Error('نوع الملف غير مدعوم');
}
return text;
}
/* =====================================
دوال رفع الملفات وإدخال النصوص
===================================== */
// رفع ملف السورس
document.getElementById('sourceFile')?.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
// إذا كان الملف PDF أو صورة، لا نقوم باستخراج النص تلقائيًا
if (file.type === 'application/pdf' || file.type.startsWith('image/')) {
return;
}
document.getElementById('processingStatus').classList.remove('hidden');
try {
const text = await processFile(file);
document.getElementById('sourceText').value = text;
} catch (error) {
console.error('Error processing source file:', error);
addError('خطأ في معالجة ملف السورس');
} finally {
document.getElementById('processingStatus').classList.add('hidden');
}
});
// رفع ملف التارجت
document.getElementById('targetFile')?.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
// إذا كان الملف PDF أو صورة، لا نقوم باستخراج النص تلقائيًا
if (file.type === 'application/pdf' || file.type.startsWith('image/')) {
return;
}
document.getElementById('processingStatus').classList.remove('hidden');
try {
const text = await processFile(file);
document.getElementById('targetText').value = text;
} catch (error) {
console.error('Error processing target file:', error);
addError('خطأ في معالجة ملف التارجت');
} finally {
document.getElementById('processingStatus').classList.add('hidden');
}
});
// رفع ملف المصادر الإضافية
document.getElementById('sourceExtraFile')?.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
document.getElementById('processingStatus').classList.remove('hidden');
try {
const text = await processFile(file);
document.getElementById('sourceExtraText').value = text;
} catch (error) {
console.error('Error processing extra source file:', error);
addError('خطأ في معالجة ملف المصدر الإضافي');
} finally {
document.getElementById('processingStatus').classList.add('hidden');
}
});
/* =====================================
دالة عرض الأخطاء والرسائل
===================================== */
function addError(message, type = 'error') {
const errorsList = document.getElementById('errorsList');
if (!errorsList) return;
const errorDiv = document.createElement('div');
errorDiv.className = `p-4 rounded-xl ${type === 'error' ? 'bg-red-50 text-red-700' : type === 'info' ? 'bg-blue-50 text-blue-700' : 'bg-yellow-50 text-yellow-700'}`;
errorDiv.innerHTML = `<div class="flex items-center">
<i class="fas fa-${type === 'error' ? 'exclamation-circle' : type === 'info' ? 'info-circle' : 'exclamation-triangle'} ml-2 text-${type === 'error' ? 'red' : type === 'info' ? 'blue' : 'yellow'}-500"></i>
<span>${message}</span>
</div>`;
errorsList.appendChild(errorDiv);
}
/* =====================================
معالجة عملية التحليل عند الضغط على الزر
===================================== */
document.getElementById('submitBtn').addEventListener('click', async () => {
const sourceText = document.getElementById('sourceText').value;
const targetText = document.getElementById('targetText').value;
// مسح الرسائل السابقة وإظهار النتائج
document.getElementById('errorsList').innerHTML = '';
document.getElementById('resultSection').classList.remove('hidden');
document.getElementById('explanationBox').classList.remove('hidden');
if (!sourceText || !targetText) {
addError('يرجى إدخال كلا النصين المصدر والهدف');
return;
}
// مقارنة عدد الكلمات وتنبيه إن وجد اختلاف
const sourceWordCount = countWords(sourceText);
const targetWordCount = countWords(targetText);
if (sourceWordCount !== targetWordCount) {
addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف عن النص الهدف (${targetWordCount})`, 'warning');
}
try {
// عرض مؤشر التقدم الأولي
const initialProgressDiv = document.createElement('div');
initialProgressDiv.className = "bg-blue-100 p-4 rounded-xl mb-4";
initialProgressDiv.innerHTML = `<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>جارٍ تقسيم النصوص للتحليل...</span>
</div>`;
document.getElementById('errorsList').appendChild(initialProgressDiv);
// تقسيم النصوص إلى أجزاء
const sourceSegments = await divideTextWithChatGPT(sourceText);
const targetSegments = await divideTextWithChatGPT(targetText);
// التحقق من عدد الأجزاء
if (sourceSegments.length !== targetSegments.length) {
// إذا كان عدد الأجزاء مختلفًا، نقوم بتعديل التقسيم
const maxSegments = Math.max(sourceSegments.length, targetSegments.length);
const minSegments = Math.min(sourceSegments.length, targetSegments.length);
// إضافة رسالة تحذير
addError(`تم تعديل تقسيم النصوص لضمان التطابق (${sourceSegments.length} مقابل ${targetSegments.length})`, 'warning');
// تعديل المصفوفة الأقصر
if (sourceSegments.length < targetSegments.length) {
// دمج الأجزاء الزائدة في التارجت مع الجزء الأخير من السورس
const extraSegments = targetSegments.slice(minSegments);
targetSegments.splice(minSegments, extraSegments.length, extraSegments.join('\n\n'));
} else {
// دمج الأجزاء الزائدة في السورس مع الجزء الأخير من التارجت
const extraSegments = sourceSegments.slice(minSegments);
sourceSegments.splice(minSegments, extraSegments.length, extraSegments.join('\n\n'));
}
}
initialProgressDiv.remove();
// تحليل الأجزاء
fullAnalysisText = await analyzeTextSegments(sourceSegments, targetSegments);
// عرض النتائج
document.getElementById('sourceTextReview').innerHTML = applyHighlights(sourceText, fullAnalysisText);
document.getElementById('targetTextReview').innerHTML = applyHighlights(targetText, fullAnalysisText);
// عرض الشرح التفصيلي
document.getElementById('explanationText').innerHTML = generateExplanation(sourceText, fullAnalysisText);
// عرض المقاطع في طريقة العرض المقسمة
displaySegmentedView();
// إعداد العرض التفاعلي
setupInteractiveView();
// إعداد المسودة
await displayDraftSections();
} catch (error) {
console.error('Error during analysis:', error);
addError('حدث خطأ أثناء التحليل: ' + error.message);
}
});
/* =====================================
التبديل بين طرق العرض المختلفة
===================================== */
document.querySelectorAll('.view-tab').forEach(tab => {
tab.addEventListener('click', function() {
// الحصول على مجموعة علامات التبويب
const tabContainer = this.closest('.view-tabs');
// إزالة الفئة النشطة من جميع علامات التبويب في نفس المجموعة
tabContainer.querySelectorAll('.view-tab').forEach(t => t.classList.remove('active'));
// إضافة الفئة النشطة إلى علامة التبويب المحددة
this.classList.add('active');
// تحديد شاشة العرض المقابلة بناءً على البيانات
const tabId = this.getAttribute('data-tab');
// إخفاء جميع محتويات العرض
const contentSelector = tabContainer.closest('div').querySelectorAll('.view-content');
contentSelector.forEach(content => content.classList.remove('active'));
// إظهار محتوى العرض المحدد
const targetContent = document.getElementById(tabId);
if (targetContent) {
targetContent.classList.add('active');
}
});
});
/* =====================================
التبديل بين عرض وإخفاء المسودة
===================================== */
document.getElementById('toggleDraftBtn').addEventListener('click', function() {
const draftSection = document.getElementById('fullTextDraftSection');
draftSection.classList.toggle('hidden');
});
/* =====================================
تنزيل التقرير بصيغة Excel
===================================== */
document.getElementById('downloadExcelBtn').addEventListener('click', function() {
// هنا يمكن إضافة كود لتنزيل التقرير بصيغة Excel
alert('سيتم تنفيذ تنزيل التقرير بصيغة Excel قريبًا');
});
/* =====================================
تنزيل التقرير بصيغة Word
===================================== */
document.getElementById('downloadWordBtn').addEventListener('click', function() {
// هنا يمكن إضافة كود لتنزيل التقرير بصيغة Word
alert('سيتم تنفيذ تنزيل التقرير بصيغة Word قريبًا');
});
</script>
</body>
</html>