|
<!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> |
|
|
|
<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"> |
|
<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(156,39,176,0.1), 0 10px 10px -5px rgba(156,39,176,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: #6366f1; |
|
color: #4f46e5; |
|
} |
|
|
|
.view-content { |
|
display: none; |
|
} |
|
|
|
.view-content.active { |
|
display: block; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gradient-to-br from-gray-100 via-blue-100 to-indigo-100 min-h-screen"> |
|
<div class="min-h-screen pb-12"> |
|
|
|
<header class="bg-gradient-to-r from-indigo-700 via-purple-700 to-pink-700 animate-gradient 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://ufastpro.com/wp-content/uploads/2025/04/471276448_1153582006774617_1534220249669349002_n.jpg" 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 rounded-2xl shadow-lg p-8 border border-gray-100"> |
|
<p class="text-lg text-gray-800"> |
|
هذا البرنامج مصمم لمقارنة النصوص التقنية وتحليلها بدقة عالية، مع تحديد الاختلافات في الأرقام، النصوص المفقودة واختلافات المعنى. يمكنك أيضًا التحقق مما إذا تم استخدام قاعدة المصطلحات أثناء التحليل وتنزيل تقرير مفصل بصيغة إكسيل أو وورد. |
|
</p> |
|
</div> |
|
</section> |
|
|
|
|
|
<main class="max-w-6xl mx-auto px-4"> |
|
|
|
<div class="bg-white 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-indigo-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-indigo-300 rounded-xl p-8 text-center hover:border-indigo-500 transition-colors duration-300 bg-indigo-50 hover:bg-indigo-100"> |
|
<label class="cursor-pointer block"> |
|
<input type="file" id="sourceFile" accept=".docx,.pdf" class="hidden"> |
|
<i class="fas fa-file-upload text-5xl text-indigo-500 mb-4"></i> |
|
<span class="text-lg text-indigo-600 group-hover:text-indigo-700">ملف السورس</span> |
|
</label> |
|
</div> |
|
|
|
<div class="group border-2 border-dashed border-pink-300 rounded-xl p-8 text-center hover:border-pink-500 transition-colors duration-300 bg-pink-50 hover:bg-pink-100"> |
|
<label class="cursor-pointer block"> |
|
<input type="file" id="targetFile" accept=".docx,.pdf" class="hidden"> |
|
<i class="fas fa-file-download text-5xl text-pink-500 mb-4"></i> |
|
<span class="text-lg text-pink-600 group-hover:text-pink-700">ملف التارجت</span> |
|
</label> |
|
</div> |
|
</div> |
|
<div id="processStatus" class="hidden mt-4"> |
|
<div class="flex items-center justify-center space-x-3 bg-indigo-100 rounded-xl p-4"> |
|
<div class="animate-spin h-8 w-8 border-4 border-indigo-600 rounded-full border-t-transparent"></div> |
|
<span class="text-lg text-indigo-700">جارٍ معالجة الملف...</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white 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-indigo-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-indigo-200 focus:border-indigo-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-pink-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-pink-200 focus:border-pink-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 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-green-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-green-300 rounded-xl p-8 text-center hover:border-green-500 transition-colors duration-300 bg-green-50 hover:bg-green-100"> |
|
<label class="cursor-pointer block"> |
|
<input type="file" id="sourceExtraFile" accept=".docx,.pdf" class="hidden"> |
|
<i class="fas fa-upload text-5xl text-green-500 mb-4"></i> |
|
<span class="text-lg text-green-600 group-hover:text-green-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-green-600 ml-2"></i> إدخال المصادر يدويًا |
|
</label> |
|
<textarea id="sourceExtraText" dir="rtl" class="w-full px-6 py-4 border-2 border-green-200 rounded-xl focus:ring-green-200 focus:border-green-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب المصادر هنا..."></textarea> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<button id="submitBtn" class="w-full bg-gradient-to-r from-indigo-600 to-pink-600 hover:from-indigo-700 hover:to-pink-700 text-white font-bold py-5 px-8 rounded-xl transition-all transform hover:scale-105 focus:ring-indigo-200 text-xl shadow-lg hover:shadow-xl mb-8 pulse-animation"> |
|
<div class="flex items-center justify-center"> |
|
<i class="fas fa-sync-alt ml-2"></i> تحليل النصوص |
|
</div> |
|
</button> |
|
|
|
|
|
<div id="resultSection" class="bg-white rounded-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-green-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> |
|
|
|
|
|
<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-indigo-600 ml-2"></i> النص المصدر (مع التعليم) |
|
</h4> |
|
<div id="sourceTextReview" class="bg-indigo-50 rounded-xl p-6 min-h-[200px] border-2 border-indigo-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-pink-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"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="explanationBox" class="bg-white 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-green-600 ml-2"></i> شرح الاختلافات |
|
</h2> |
|
<div id="explanationText" class="text-lg text-gray-700"></div> |
|
</div> |
|
|
|
|
|
<div id="fullTextDraftSection" class="bg-white 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"> |
|
|
|
</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-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded"> |
|
تنزيل التقرير (Word) |
|
</button> |
|
</div> |
|
</div> |
|
</main> |
|
</div> |
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
const DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions'; |
|
const DEEPSEEK_API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01'; |
|
|
|
|
|
const CHATGPT_API_URL = 'https://api.openai.com/v1/chat/completions'; |
|
const CHATGPT_API_KEY = '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'; |
|
|
|
let fullAnalysisText = ''; |
|
let analysisSegments = []; |
|
|
|
|
|
|
|
|
|
|
|
const ANALYSIS_PROMPT = `أنت خبير لغوي متخصص في مراجعة الترجمة التقنية. مهمتك مقارنة النص المصدر والنص الهدف بدقة عالية مع العلم أن النصوص مليانة بالأخطاء والنواقص. |
|
لا تقم بإزالة أو تعديل العلامات التالية: |
|
• الأرقام: تحافظ على علامات < و >. |
|
• النصوص المفقودة: تحافظ على علامات __ و __. |
|
• اختلافات المعنى: تحافظ على علامات [MEANING] و [/MEANING]. |
|
|
|
اعتمد النص المصدر كأساس للمقارنة، وقم بتحديد: |
|
1. اختلافات الأرقام باستخدام <الرقم_في_المصدر> → <الرقم_في_الهدف>. |
|
2. النصوص المفقودة كما هي بين علامتي __. |
|
3. اختلافات المعنى باستخدام [MEANING] مع الحفاظ على التعليم. |
|
|
|
النص المصدر: |
|
{source} |
|
|
|
النص الهدف: |
|
{target}`; |
|
|
|
|
|
const TEXT_DIVISION_PROMPT = `قم بتقسيم النص التالي إلى أجزاء منطقية للتحليل. |
|
يجب أن يكون كل جزء وحدة متماسكة (فقرة، قسم، إلخ) لا تتجاوز 500 كلمة. |
|
أعد النص كمصفوفة JSON بالأجزاء. الصيغة: {"segments": ["جزء1", "جزء2", ...]} |
|
|
|
النص للتقسيم: |
|
{text}`; |
|
|
|
|
|
const SYNC_WORD_PROMPT = `قم بتحليل النص التالي وإيجاد آخر كلمة أو جملة قصيرة (لا تزيد عن 3 كلمات) مناسبة ليمكن استخدامها كنقطة تزامن بين الفقرات. اختر كلمة أو جملة تظهر بشكل طبيعي في النص وتكون مناسبة للتقسيم. |
|
أعِد فقط الكلمة أو الجملة المحددة دون أي إضافات أخرى. |
|
|
|
النص: |
|
{text}`; |
|
|
|
|
|
|
|
|
|
async function divideTextWithChatGPT(text) { |
|
try { |
|
|
|
if (countWords(text) <= 500) { |
|
return [text]; |
|
} |
|
|
|
|
|
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 |
|
}; |
|
|
|
|
|
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(); |
|
|
|
|
|
try { |
|
const jsonResponse = JSON.parse(content); |
|
if (jsonResponse.segments && Array.isArray(jsonResponse.segments)) { |
|
return jsonResponse.segments; |
|
} |
|
} catch (e) { |
|
|
|
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 { |
|
|
|
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 |
|
}; |
|
|
|
|
|
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 "نهاية الفقرة"; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
async function analyzeTextSegments(sourceSegments, targetSegments) { |
|
|
|
if (sourceSegments.length !== targetSegments.length) { |
|
throw new Error('عدد أجزاء المصدر والهدف غير متطابق'); |
|
} |
|
|
|
let combinedAnalysis = ''; |
|
const progressDiv = document.createElement('div'); |
|
progressDiv.className = "bg-indigo-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-indigo-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]); |
|
|
|
|
|
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-indigo-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-pink-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 countWords(text) { |
|
return text.trim().split(/\s+/).filter(word => word !== "").length; |
|
} |
|
|
|
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-indigo-600">الأرقام</span>'); |
|
text = text.replace(/المفقودة/g, '<span class="font-bold text-pink-600">المفقودة</span>'); |
|
text = text.replace(/المعنى/g, '<span class="font-bold text-green-600">المعنى</span>'); |
|
|
|
|
|
text = text.replace(/<([^<>]+)>/g, '<span class="highlight-number"><$1></span>'); |
|
text = text.replace(/__(.*?)__/g, '<span class="highlight-missing">__$1__</span>'); |
|
text = text.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '<span class="highlight-meaning">$1</span>'); |
|
|
|
return text; |
|
} |
|
|
|
function 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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 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'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function generateExplanation(sourceText, analysisOutput) { |
|
let steps = []; |
|
|
|
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>`); |
|
} |
|
} |
|
|
|
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>`); |
|
} |
|
} |
|
|
|
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>`; |
|
} |
|
|
|
|
|
|
|
|
|
async function processFile(file) { |
|
let text = ""; |
|
if (file.type === 'application/pdf') { |
|
const form = new FormData(); |
|
form.append('image', file); |
|
const response = await fetch('https://demo.api4ai.cloud/ocr/v1/results', { |
|
method: 'POST', |
|
body: form, |
|
headers: { 'A4A-CLIENT-APP-ID': 'sample' } |
|
}); |
|
const data = await response.json(); |
|
text = data.results[0].entities[0].objects[0].entities[0].text; |
|
} 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; |
|
document.getElementById('processStatus').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('processStatus').classList.add('hidden'); |
|
} |
|
}); |
|
|
|
document.getElementById('targetFile')?.addEventListener('change', async (event) => { |
|
const file = event.target.files[0]; |
|
if (!file) return; |
|
document.getElementById('processStatus').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('processStatus').classList.add('hidden'); |
|
} |
|
}); |
|
|
|
document.getElementById('sourceExtraFile')?.addEventListener('change', async (event) => { |
|
const file = event.target.files[0]; |
|
if (!file) return; |
|
document.getElementById('processStatus').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('processStatus').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' : 'bg-yellow-50 text-yellow-700'}`; |
|
errorDiv.innerHTML = `<div class="flex items-center"> |
|
<i class="fas fa-${type === 'error' ? 'exclamation-circle' : 'info-circle'} ml-2 text-${type === 'error' ? 'red' : '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-indigo-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-indigo-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(); |
|
|
|
|
|
await displayDraftSections(); |
|
|
|
} catch (error) { |
|
console.error('Error during analysis:', error); |
|
addError('حدث خطأ أثناء التحليل: ' + error.message); |
|
} |
|
}); |
|
|
|
|
|
|
|
|
|
document.querySelectorAll('.view-tab').forEach(tab => { |
|
tab.addEventListener('click', function() { |
|
|
|
document.querySelectorAll('.view-tab').forEach(t => t.classList.remove('active')); |
|
|
|
this.classList.add('active'); |
|
|
|
|
|
document.querySelectorAll('.view-content').forEach(content => content.classList.remove('active')); |
|
|
|
const tabId = this.getAttribute('data-tab'); |
|
document.getElementById(tabId).classList.add('active'); |
|
}); |
|
}); |
|
|
|
|
|
|
|
|
|
document.getElementById('toggleDraftBtn').addEventListener('click', function() { |
|
const draftSection = document.getElementById('fullTextDraftSection'); |
|
draftSection.classList.toggle('hidden'); |
|
}); |
|
|
|
|
|
|
|
|
|
document.getElementById('downloadExcelBtn').addEventListeneهةبr('click', function() { |
|
|
|
alert('سيتم تنفيذ تنزيل التقرير بصيغة Excel قريبًا'); |
|
}); |
|
|
|
|
|
|
|
|
|
document.getElementById('downloadWordBtn').addEventListener('click', function() { |
|
|
|
alert('سيتم تنفيذ تنزيل التقرير بصيغة Word قريبًا'); |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
|