TRANSLATE / script.js
joermd's picture
Create script.js
8a638e8 verified
// Global variables
let sourceText = "";
let targetText = "";
let sourceFile = null;
let targetFile = null;
let sourceFileType = "";
let targetFileType = "";
let documentType = "regular"; // Default document type: "regular" or "official"
let analysis = null;
let correctedSourceText = ""; // For storing corrected text in case of official documents
let detectedCountry = ""; // For storing detected country from document
// Initialize when DOM is loaded
document.addEventListener("DOMContentLoaded", function() {
initializeApp();
});
// Initialize application
function initializeApp() {
// Initialize document type selector
initDocumentTypeSelector();
// Initialize file upload listeners
initFileUploadListeners();
// Initialize text area listeners
initTextAreaListeners();
// Initialize button listeners
initButtonListeners();
// Initialize display options
initDisplayOptions();
// Initialize filter options
initFilterOptions();
}
// Initialize document type selector
function initDocumentTypeSelector() {
// Create document type selector (radio buttons)
const selectorHtml = `
<div class="document-type-selector mb-4">
<p class="font-bold mb-2">نوع الملف:</p>
<div class="flex gap-4">
<label class="flex items-center">
<input type="radio" name="documentType" value="regular" checked class="mr-2">
<span>ملف عادي</span>
</label>
<label class="flex items-center">
<input type="radio" name="documentType" value="official" class="mr-2">
<span>مستند رسمي</span>
</label>
</div>
</div>
`;
// Insert before source file upload section
const sourceFileSection = document.querySelector("#sourceFileSection");
sourceFileSection.insertAdjacentHTML('beforebegin', selectorHtml);
// Add event listeners to the radio buttons
document.querySelectorAll('input[name="documentType"]').forEach(radio => {
radio.addEventListener('change', function() {
documentType = this.value;
console.log(`Document type changed to: ${documentType}`);
});
});
}
// Initialize file upload listeners
function initFileUploadListeners() {
// Source file upload
const sourceFileInput = document.getElementById("sourceFileInput");
if (sourceFileInput) {
sourceFileInput.addEventListener("change", function(e) {
handleFileUpload(e, "source");
});
}
// Target file upload
const targetFileInput = document.getElementById("targetFileInput");
if (targetFileInput) {
targetFileInput.addEventListener("change", function(e) {
handleFileUpload(e, "target");
});
}
// Drag and drop zones
initDragAndDropZones();
}
// Initialize drag and drop zones
function initDragAndDropZones() {
const sourceDropZone = document.getElementById("sourceDropZone");
const targetDropZone = document.getElementById("targetDropZone");
// Source drop zone
if (sourceDropZone) {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
sourceDropZone.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => {
sourceDropZone.addEventListener(eventName, function() {
this.classList.add('bg-blue-100');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
sourceDropZone.addEventListener(eventName, function() {
this.classList.remove('bg-blue-100');
}, false);
});
sourceDropZone.addEventListener('drop', function(e) {
const dt = e.dataTransfer;
if (dt.files.length) {
document.getElementById('sourceFileInput').files = dt.files;
handleFileUpload({ target: { files: dt.files } }, "source");
}
}, false);
}
// Target drop zone
if (targetDropZone) {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
targetDropZone.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => {
targetDropZone.addEventListener(eventName, function() {
this.classList.add('bg-blue-100');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
targetDropZone.addEventListener(eventName, function() {
this.classList.remove('bg-blue-100');
}, false);
});
targetDropZone.addEventListener('drop', function(e) {
const dt = e.dataTransfer;
if (dt.files.length) {
document.getElementById('targetFileInput').files = dt.files;
handleFileUpload({ target: { files: dt.files } }, "target");
}
}, false);
}
}
// Prevent default behavior for drag and drop
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Initialize text area listeners
function initTextAreaListeners() {
const sourceTextArea = document.getElementById("sourceTextArea");
const targetTextArea = document.getElementById("targetTextArea");
if (sourceTextArea) {
sourceTextArea.addEventListener("input", function() {
sourceText = this.value;
});
}
if (targetTextArea) {
targetTextArea.addEventListener("input", function() {
targetText = this.value;
});
}
}
// Initialize button listeners
function initButtonListeners() {
// Analyze button
const analyzeBtn = document.getElementById("analyzeBtn");
if (analyzeBtn) {
analyzeBtn.addEventListener("click", startAnalysis);
}
// Export report button
const exportReportBtn = document.getElementById("exportReportBtn");
if (exportReportBtn) {
exportReportBtn.addEventListener("click", exportReport);
}
// Clear all button
const clearAllBtn = document.getElementById("clearAllBtn");
if (clearAllBtn) {
clearAllBtn.addEventListener("click", clearAll);
}
}
// Initialize display options
function initDisplayOptions() {
// Remove classic display option as per requirements
const displayOptions = document.querySelectorAll('.display-option');
displayOptions.forEach(option => {
if (option.dataset.display === 'classic') {
option.remove();
}
});
// Set split view as default
const splitViewOption = document.querySelector('.display-option[data-display="split"]');
if (splitViewOption) {
splitViewOption.classList.add('active');
setDisplayMode('split');
}
// Add display option listeners
document.querySelectorAll('.display-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.display-option').forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
setDisplayMode(this.dataset.display);
});
});
}
// Initialize filter options
function initFilterOptions() {
document.querySelectorAll('.filter-option').forEach(option => {
option.addEventListener('click', function() {
this.classList.toggle('active');
applyFilters();
});
});
}
// Handle file upload
function handleFileUpload(event, type) {
const file = event.target.files[0];
if (!file) return;
// Store file
if (type === "source") {
sourceFile = file;
document.getElementById("sourceFileName").textContent = file.name;
} else {
targetFile = file;
document.getElementById("targetFileName").textContent = file.name;
}
// Process file based on type
processFile(file, type);
}
// Process file based on file extension
async function processFile(file, textType) {
const fileExtension = file.name.split('.').pop().toLowerCase();
const fileType = file.type;
// Show loading indicator
toggleLoading(true, textType);
try {
let extractedText = "";
// Process based on file extension/type
if (fileType.includes('pdf') || fileExtension === 'pdf') {
extractedText = await processPDF(file);
} else if (fileType.includes('image') || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension)) {
extractedText = await processImage(file);
} else if (fileExtension === 'docx' || fileExtension === 'doc') {
extractedText = await processDocx(file);
} else if (['xlsx', 'xls', 'csv'].includes(fileExtension)) {
extractedText = await processExcel(file);
} else if (fileType.includes('text') || fileExtension === 'txt') {
extractedText = await processTextFile(file);
} else {
throw new Error("Unsupported file format. Please use PDF, image, Word, Excel, or text files.");
}
// If document type is "official", correct the fixed terms
if (documentType === "official" && textType === "source") {
showNotification("جاري تصحيح الثوابت في المستند الرسمي...", "info");
correctedSourceText = await correctOfficialDocument(extractedText);
// Update extracted text with corrected text
extractedText = correctedSourceText;
showNotification("تم تصحيح الثوابت بنجاح", "success");
}
// Update UI with extracted text
if (textType === "source") {
sourceText = extractedText;
sourceFileType = fileExtension;
document.getElementById("sourceTextArea").value = extractedText;
} else {
targetText = extractedText;
targetFileType = fileExtension;
document.getElementById("targetTextArea").value = extractedText;
}
// Show preview if it's a PDF
if (fileType.includes('pdf') || fileExtension === 'pdf') {
showPDFPreview(file, textType);
}
// Show OCR results
showOCRResults(extractedText, textType);
// Enable analyze button if both texts are available
if (sourceText && targetText) {
document.getElementById("analyzeBtn").disabled = false;
}
} catch (error) {
console.error("Error processing file:", error);
showNotification(`خطأ في معالجة الملف: ${error.message}`, "error");
} finally {
// Hide loading indicator
toggleLoading(false, textType);
}
}
// Process PDF file
async function processPDF(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = async function() {
try {
const typedArray = new Uint8Array(this.result);
// Load the PDF using pdf.js
const pdf = await pdfjsLib.getDocument({ data: typedArray }).promise;
let text = "";
// Extract text from all pages
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items.map(item => item.str).join(' ');
text += pageText + "\n";
}
resolve(text);
} catch (error) {
console.error("Error extracting text from PDF:", error);
reject(error);
}
};
fileReader.onerror = function() {
reject(new Error("Error reading the PDF file."));
};
fileReader.readAsArrayBuffer(file);
});
}
// Process image using OCR
async function processImage(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = async function() {
try {
// Here we would typically call an OCR API
// For now, we'll simulate it with a placeholder
showNotification("جاري استخراج النص من الصورة...", "info");
// This is where you would call your OCR API
// For example, using Tesseract.js or a cloud OCR API
// Simulate OCR processing delay
await new Promise(res => setTimeout(res, 2000));
// Placeholder for OCR result
// In a real implementation, replace this with actual OCR API call
const ocrResult = "هذا نص تم استخراجه من الصورة باستخدام تقنية OCR.\nيمكنك استبدال هذا النص بنتائج OCR الفعلية من API الخاص بك.";
resolve(ocrResult);
} catch (error) {
console.error("Error processing image:", error);
reject(error);
}
};
fileReader.onerror = function() {
reject(new Error("Error reading the image file."));
};
fileReader.readAsDataURL(file);
});
}
// Process Word document
async function processDocx(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = async function() {
try {
// Use mammoth.js to extract text from DOCX
const arrayBuffer = this.result;
const result = await mammoth.extractRawText({ arrayBuffer });
resolve(result.value);
} catch (error) {
console.error("Error extracting text from DOCX:", error);
reject(error);
}
};
fileReader.onerror = function() {
reject(new Error("Error reading the Word document."));
};
fileReader.readAsArrayBuffer(file);
});
}
// Process Excel file
async function processExcel(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = async function() {
try {
const data = new Uint8Array(this.result);
const workbook = XLSX.read(data, { type: 'array' });
// Extract text from all sheets
let text = "";
workbook.SheetNames.forEach(sheetName => {
const worksheet = workbook.Sheets[sheetName];
const sheetText = XLSX.utils.sheet_to_txt(worksheet);
text += `[Sheet: ${sheetName}]\n${sheetText}\n\n`;
});
resolve(text);
} catch (error) {
console.error("Error extracting text from Excel:", error);
reject(error);
}
};
fileReader.onerror = function() {
reject(new Error("Error reading the Excel file."));
};
fileReader.readAsArrayBuffer(file);
});
}
// Process text file
async function processTextFile(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = function() {
try {
resolve(this.result);
} catch (error) {
console.error("Error reading text file:", error);
reject(error);
}
};
fileReader.onerror = function() {
reject(new Error("Error reading the text file."));
};
fileReader.readAsText(file);
});
}
// Correct official document using DeepSeek API
async function correctOfficialDocument(text) {
try {
// First, detect the country from the document
detectedCountry = await detectCountryFromDocument(text);
console.log("Detected country:", detectedCountry);
// Prepare the prompt for DeepSeek API based on the detected country
const prompt = prepareFixedTermsPrompt(text, detectedCountry);
// Call DeepSeek API for correction
const correctionResponse = await callDeepSeekAPI(prompt, "correction");
// Extract the corrected text from the response
return extractCorrectedText(correctionResponse, text);
} catch (error) {
console.error("Error correcting official document:", error);
showNotification("حدث خطأ أثناء تصحيح المستند الرسمي", "error");
return text; // Return original text if correction fails
}
}
// Detect country from document text
async function detectCountryFromDocument(text) {
try {
const prompt = `
أنت خبير في تحليل المستندات الرسمية. قم بتحليل النص التالي وتحديد الدولة التي ينتمي إليها هذا المستند بناءً على المحتوى، الصياغة، المصطلحات المستخدمة، أو أي مؤشرات أخرى.
أعطني اسم الدولة فقط دون أي توضيح إضافي.
النص:
${text}
`;
// Call DeepSeek API for country detection
const response = await callDeepSeekAPI(prompt, "country-detection");
// Process the response to extract just the country name
let country = response.trim();
// If multiple lines, take the first line
if (country.includes('\n')) {
country = country.split('\n')[0];
}
return country;
} catch (error) {
console.error("Error detecting country:", error);
return "غير محدد"; // Default if detection fails
}
}
// Prepare prompt for fixed terms correction based on country
function prepareFixedTermsPrompt(text, country) {
return `
أنت خبير في تصحيح المستندات الرسمية للدولة: ${country}.
مهمتك هي تصحيح المصطلحات الثابتة فقط في المستند الرسمي التالي، مع الحفاظ على البيانات المتغيرة كما هي تماماً.
قواعد التصحيح:
1. صحح فقط المصطلحات والعبارات الثابتة مثل "الاسم"، "تاريخ الميلاد"، "الجنسية"، "الرقم القومي"، "رقم الهوية".
2. لا تغير أبداً البيانات المتغيرة مثل أسماء الأشخاص، التواريخ، الأرقام، إلخ.
3. حافظ على التنسيق الأصلي للنص.
4. أعد النص كاملاً بعد التصحيح.
على سبيل المثال:
- إذا كان النص يحتوي على "الأثم: محمد أحمد"، قم بتصحيحه إلى "الاسم: محمد أحمد"
- إذا كان النص يحتوي على "تاريخ الميلات: 1990/05/15"، قم بتصحيحه إلى "تاريخ الميلاد: 1990/05/15"
النص المراد تصحيحه:
${text}
أعد النص كاملاً بعد التصحيح دون أي توضيحات إضافية.
`;
}
// Extract corrected text from API response
function extractCorrectedText(response, originalText) {
// This function should parse the API response to extract just the corrected text
// If the API returns explanations or other content, strip those out
// For now, we'll just return the response as is, assuming it's clean
return response;
}
// Call DeepSeek API
async function callDeepSeekAPI(prompt, type) {
// This function would typically make an API call to DeepSeek
// For now, we'll simulate it with a placeholder response
console.log(`Calling DeepSeek API for ${type} with prompt:`, prompt);
// Simulate API delay
await new Promise(res => setTimeout(res, 3000));
// Simulate different responses based on the type of request
if (type === "correction") {
// Simulate a corrected document
return prompt.replace("الأثم:", "الاسم:")
.replace("تاريخ الميلات:", "تاريخ الميلاد:")
.replace("الجنسيه:", "الجنسية:")
.replace("رقم القومى:", "الرقم القومي:");
} else if (type === "country-detection") {
// Simulate country detection
return "مصر";
} else if (type === "analysis") {
// Simulate analysis response
return {
differences: [
{
sourceText: "هذا النص الأصلي يحتوي على خطأ.",
targetText: "هذا النص المترجم يحتوي على خطأ مختلف.",
errorType: "grammar",
explanation: "الجملة في النص المترجم تحتوي على خطأ نحوي.",
severity: "medium"
},
// More differences...
],
summary: "تم العثور على 5 اختلافات بين النصين، منها 2 خطأ نحوي و3 أخطاء إملائية."
};
}
return "عذراً، حدث خطأ في معالجة طلبك.";
}
// Start analysis of texts
async function startAnalysis() {
if (!sourceText || !targetText) {
showNotification("يرجى إدخال أو تحميل النصين المصدر والهدف", "error");
return;
}
try {
// Show loading indicator
toggleLoading(true, "analysis");
showNotification("جاري تحليل النصوص...", "info");
// Prepare prompt for analysis
const analysisPrompt = `
قم بتحليل النصين التاليين ومقارنتهما لتحديد الاختلافات والأخطاء:
النص المصدر:
${sourceText}
النص الهدف:
${targetText}
قم بتحديد الاختلافات التالية:
1. الأخطاء النحوية
2. الأخطاء الإملائية
3. الاختلافات في المعنى
4. الكلمات أو العبارات المفقودة
5. الإضافات غير الضرورية
لكل اختلاف، قدم:
- النص المصدر
- النص الهدف
- نوع الخطأ
- شرح الخطأ
- مستوى خطورة الخطأ (منخفض، متوسط، مرتفع)
`;
// Call DeepSeek API for analysis
const apiResponse = await callDeepSeekAPI(analysisPrompt, "analysis");
// Process the analysis results
analysis = apiResponse; // In a real implementation, this would parse the API response
// Display analysis results
displayAnalysisResults(analysis);
// Enable report export
document.getElementById("exportReportBtn").disabled = false;
showNotification("تم الانتهاء من التحليل بنجاح", "success");
} catch (error) {
console.error("Error during analysis:", error);
showNotification(`خطأ في التحليل: ${error.message}`, "error");
} finally {
// Hide loading indicator
toggleLoading(false, "analysis");
}
}
// Display analysis results with improved highlighting and explanation
function displayAnalysisResults(analysis) {
const resultsContainer = document.getElementById("resultsContainer");
resultsContainer.innerHTML = "";
// Create summary section
const summarySection = document.createElement("div");
summarySection.className = "mb-6 p-4 bg-gray-50 rounded-lg";
summarySection.innerHTML = `
<h3 class="text-xl font-bold mb-2">ملخص التحليل</h3>
<p>${analysis.summary}</p>
`;
resultsContainer.appendChild(summarySection);
// Create differences section with improved display
const differencesSection = document.createElement("div");
differencesSection.className = "differences-section";
// Group differences by paragraphs for better organization
const groupedDifferences = groupDifferencesByParagraph(analysis.differences);
// Create each paragraph section
let paragraphIndex = 1;
for (const [paragraph, differences] of Object.entries(groupedDifferences)) {
const paragraphSection = document.createElement("div");
paragraphSection.className = "paragraph-section mb-8 p-4 bg-white rounded-lg shadow";
paragraphSection.innerHTML = `
<h3 class="text-lg font-bold mb-4">فقرة ${paragraphIndex}</h3>
<div class="paragraph-content mb-4">
<p class="whitespace-pre-wrap">${highlightDifferencesInParagraph(paragraph, differences)}</p>
</div>
<div class="paragraph-differences">
<h4 class="font-bold mb-2">الاختلافات المكتشفة:</h4>
<ul class="differences-list">
${differences.map(diff => `
<li class="difference-item mb-4 p-3 border-r-4 border-${getSeverityColor(diff.severity)} bg-${getSeverityColor(diff.severity)}-50">
<div class="flex justify-between">
<span class="font-bold">${getErrorTypeLabel(diff.errorType)}</span>
<span class="severity-badge bg-${getSeverityColor(diff.severity)}-200 text-${getSeverityColor(diff.severity)}-800 px-2 py-1 rounded text-sm">
${getSeverityLabel(diff.severity)}
</span>
</div>
<div class="mt-2">
<div class="source-text mb-1">
<span class="font-bold ml-1">النص الأصلي:</span>
<span class="text-gray-800">${diff.sourceText}</span>
</div>
<div class="target-text mb-1">
<span class="font-bold ml-1">النص المترجم:</span>
<span class="text-gray-800">${diff.targetText}</span>
</div>
<div class="explanation mt-2 p-2 bg-gray-50 rounded">
<span class="font-bold">الشرح:</span>
<p>${diff.explanation}</p>
</div>
</div>
</li>
`).join('')}
</ul>
</div>
`;
differencesSection.appendChild(paragraphSection);
paragraphIndex++;
}
resultsContainer.appendChild(differencesSection);
// Show results container
document.getElementById("resultsSection").classList.remove("hidden");
// Add tooltip popups to highlighted differences
addTooltipsToHighlights();
}
// Group differences by paragraph for better organization
function groupDifferencesByParagraph(differences) {
const paragraphMap = {};
differences.forEach(diff => {
// Use the source text to determine the paragraph
const paragraph = extractParagraphFromText(diff.sourceText, sourceText);
if (!paragraphMap[paragraph]) {
paragraphMap[paragraph] = [];
}
paragraphMap[paragraph].push(diff);
});
return paragraphMap;
}
// Extract the paragraph containing the given text
function extractParagraphFromText(snippet, fullText) {
// Split the full text into paragraphs
const paragraphs = fullText.split(/\n\s*\n/);
// Find the paragraph containing the snippet
for (const paragraph of paragraphs) {
if (paragraph.includes(snippet)) {
return paragraph;
}
}
// If not found, return the snippet itself
return snippet;
}
// Highlight differences in paragraph
function highlightDifferencesInParagraph(paragraph, differences) {
let highlightedText = paragraph;
// Sort differences by their position in the paragraph (to handle overlaps correctly)
differences.sort((a, b) => {
const posA = paragraph.indexOf(a.sourceText);
const posB = paragraph.indexOf(b.sourceText);
return posA - posB;
});
// Apply highlights to each difference
let offset = 0;
differences.forEach(diff => {
const diffId = `diff-${Math.random().toString(36).substr(2, 9)}`;
const originalText = diff.sourceText;
const startPos = paragraph.indexOf(originalText, offset);
if (startPos !== -1) {
// Find the sentence containing the difference
const sentenceMatch = findSentenceContaining(paragraph, startPos, originalText.length);
if (sentenceMatch) {
const { start, end } = sentenceMatch;
const sentence = paragraph.substring(start, end);
// Create highlighted version of the sentence
const highlightedSentence = `<span class="highlighted-sentence bg-${getSeverityColor(diff.severity)}-100 cursor-pointer" data-diff-id="${diffId}">${sentence}</span>`;
// Replace the sentence in the text
highlightedText = highlightedText.substring(0, start) + highlightedSentence + highlightedText.substring(end);
// Update offset to account for the added HTML
offset = end + (highlightedSentence.length - sentence.length);
// Store difference details for the tooltip
window.diffDetails = window.diffDetails || {};
window.diffDetails[diffId] = diff;
}
}
});
return highlightedText;
}
// Find the sentence containing the difference
function findSentenceContaining(text, position, length) {
// Look for sentence boundaries (period, question mark, exclamation mark followed by space or newline)
const sentenceEnders = ['. ', '? ', '! ', '.\n', '?\n', '!\n'];
let start = position;
while (start > 0) {
let foundBoundary = false;
for (const ender of sentenceEnders) {
const endPos = text.lastIndexOf(ender, start);
if (endPos !== -1 && endPos + ender.length <= start) {
start = endPos + ender.length;
foundBoundary = true;
break;
}
}
if (foundBoundary || start === 0) break;
start--;
}
let end = position + length;
while (end < text.length) {
let foundBoundary = false;
for (const ender of sentenceEnders) {
const endPos = text.indexOf(ender, end - ender.length + 1);
if (endPos !== -1 && endPos >= end - ender.length + 1) {
end = endPos + ender.length;
foundBoundary = true;
break;
}
}
if (foundBoundary || end === text.length) break;
end++;
}
return { start, end };
}
// Add tooltips to highlighted differences
function addTooltipsToHighlights() {
document.querySelectorAll('.highlighted-sentence').forEach(el => {
el.addEventListener('click', function() {
const diffId = this.dataset.diffId;
const diff = window.diffDetails[diffId];
if (diff) {
showDifferencePopup(diff, this);
}
});
});
}
// Show popup with difference details
function showDifferencePopup(diff, element) {
// Remove any existing popups
const existingPopup = document.getElementById('difference-popup');
if (existingPopup) {
existingPopup.remove();
}
// Create popup element
const popup = document.createElement('div');
popup.id = 'difference-popup';
popup.className = 'fixed bg-white rounded-lg shadow-lg p-4 max-w-md z-50';
popup.style.maxWidth = '90vw';
// Create popup content
popup.innerHTML = `
<div class="flex justify-between items-center mb-2">
<h3 class="font-bold text-lg">${getErrorTypeLabel(diff.errorType)}</h3>
<span class="severity-badge bg-${getSeverityColor(diff.severity)}-200 text-${getSeverityColor(diff.severity)}-800 px-2 py-1 rounded text-sm">
${getSeverityLabel(diff.severity)}
</span>
</div>
<div class="source-text mb-2">
<span class="font-bold block mb-1">النص الأصلي:</span>
<p class="bg-gray-50 p-2 rounded">${diff.sourceText}</p>
</div>
<div class="target-text mb-2">
<span class="font-bold block mb-1">النص المترجم:</span>
<p class="bg-gray-50 p-2 rounded">${diff.targetText}</p>
</div>
<div class="explanation mb-3">
<span class="font-bold block mb-1">الشرح:</span>
<p class="bg-gray-50 p-2 rounded">${diff.explanation}</p>
</div>
<button id="close-popup" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-4 rounded">
إغلاق
</button>
`;
// Position the popup near the element
document.body.appendChild(popup);
positionPopup(popup, element);
// Add close button event listener
document.getElementById('close-popup').addEventListener('click', function() {
popup.remove();
});
// Close popup when clicking outside
document.addEventListener('click', function closePopup(e) {
if (!popup.contains(e.target) && e.target !== element) {
popup.remove();
document.removeEventListener('click', closePopup);
}
});
}
// Position popup near the target element
function positionPopup(popup, element) {
const rect = element.getBoundingClientRect();
const popupRect = popup.getBoundingClientRect();
// Default position below the element
let top = rect.bottom + window.scrollY + 10;
let left = rect.left + window.scrollX;
// Check if popup would go below viewport
if (top + popupRect.height > window.innerHeight + window.scrollY) {
// Position above the element instead
top = rect.top + window.scrollY - popupRect.height - 10;
}
// Ensure popup doesn't go off-screen horizontally
if (left + popupRect.width > window.innerWidth) {
left = window.innerWidth - popupRect.width - 10;
}
popup.style.top = `${top}px`;
popup.style.left = `${left}px`;
}
// Set display mode
function setDisplayMode(mode) {
const resultsContainer = document.getElementById("resultsContainer");
// Remove existing display mode classes
resultsContainer.classList.remove('display-classic', 'display-split', 'display-side-by-side');
// Add new display mode class
resultsContainer.classList.add(`display-${mode}`);
}
// Apply filters to displayed results
function applyFilters() {
const activeFilters = Array.from(document.querySelectorAll('.filter-option.active'))
.map(el => el.dataset.filter);
// Show all differences initially
document.querySelectorAll('.difference-item').forEach(item => {
item.classList.remove('hidden');
});
// If no filters are active, show all
if (activeFilters.length === 0) {
return;
}
// Hide differences that don't match active filters
document.querySelectorAll('.difference-item').forEach(item => {
const errorType = item.querySelector('.error-type').textContent;
const severity = item.querySelector('.severity-badge').textContent;
const matchesFilter = activeFilters.some(filter => {
if (filter.startsWith('type-')) {
return errorType === filter.replace('type-', '');
} else if (filter.startsWith('severity-')) {
return severity === filter.replace('severity-', '');
}
return false;
});
if (!matchesFilter) {
item.classList.add('hidden');
}
});
}
// Export analysis report
function exportReport() {
if (!analysis) {
showNotification("لا توجد نتائج تحليل للتصدير", "error");
return;
}
try {
// Create report content
const reportContent = generateReportContent();
// Create blob and download link
const blob = new Blob([reportContent], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `تقرير_تحليل_الترجمة_${new Date().toISOString().slice(0, 10)}.txt`;
a.click();
URL.revokeObjectURL(url);
showNotification("تم تصدير التقرير بنجاح", "success");
} catch (error) {
console.error("Error exporting report:", error);
showNotification("حدث خطأ أثناء تصدير التقرير", "error");
}
}
// Generate report content
function generateReportContent() {
let content = "تقرير تحليل الترجمة\n";
content += "===================\n\n";
// Add date and time
content += `تاريخ التحليل: ${new Date().toLocaleString('ar-EG')}\n\n`;
// Add summary
content += "ملخص التحليل:\n";
content += "-------------\n";
content += analysis.summary + "\n\n";
// Add detailed differences
content += "تفاصيل الاختلافات:\n";
content += "------------------\n\n";
analysis.differences.forEach((diff, index) => {
content += `${index + 1}. ${getErrorTypeLabel(diff.errorType)} (${getSeverityLabel(diff.severity)})\n`;
content += ` النص الأصلي: ${diff.sourceText}\n`;
content += ` النص المترجم: ${diff.targetText}\n`;
content += ` الشرح: ${diff.explanation}\n\n`;
});
// Add footer
content += "تم إنشاء هذا التقرير بواسطة نظام تحليل الترجمة - شركة الريحان للترجمة";
return content;
}
// Clear all inputs and results
function clearAll() {
// Clear text areas
document.getElementById("sourceTextArea").value = "";
document.getElementById("targetTextArea").value = "";
// Clear file names
document.getElementById("sourceFileName").textContent = "لم يتم اختيار ملف";
document.getElementById("targetFileName").textContent = "لم يتم اختيار ملف";
// Clear OCR results
document.getElementById("sourceOCRResults").innerHTML = "";
document.getElementById("targetOCRResults").innerHTML = "";
// Clear analysis results
document.getElementById("resultsContainer").innerHTML = "";
document.getElementById("resultsSection").classList.add("hidden");
// Reset PDF previews
document.getElementById("sourcePDFPreview").innerHTML = "";
document.getElementById("targetPDFPreview").innerHTML = "";
document.getElementById("sourcePDFSection").classList.add("hidden");
document.getElementById("targetPDFSection").classList.add("hidden");
// Reset global variables
sourceText = "";
targetText = "";
sourceFile = null;
targetFile = null;
sourceFileType = "";
targetFileType = "";
analysis = null;
correctedSourceText = "";
detectedCountry = "";
// Reset document type to "regular"
document.querySelector('input[name="documentType"][value="regular"]').checked = true;
documentType = "regular";
// Disable buttons
document.getElementById("analyzeBtn").disabled = true;
document.getElementById("exportReportBtn").disabled = true;
showNotification("تم مسح جميع البيانات", "info");
}
// Show PDF preview
function showPDFPreview(file, type) {
const previewSection = document.getElementById(`${type}PDFSection`);
const previewContainer = document.getElementById(`${type}PDFPreview`);
// Clear previous preview
previewContainer.innerHTML = "";
// Create PDF viewer
const viewer = document.createElement("div");
viewer.className = "pdf-viewer";
// Create PDF embed
const embed = document.createElement("embed");
embed.src = URL.createObjectURL(file);
embed.type = "application/pdf";
embed.width = "100%";
embed.height = "500px";
viewer.appendChild(embed);
previewContainer.appendChild(viewer);
// Show preview section
previewSection.classList.remove("hidden");
}
// Show OCR results
function showOCRResults(text, type) {
const resultsContainer = document.getElementById(`${type}OCRResults`);
// Create results display
resultsContainer.innerHTML = `
<div class="ocr-results p-3 bg-gray-50 rounded max-h-60 overflow-y-auto">
<pre class="whitespace-pre-wrap text-sm">${text}</pre>
</div>
`;
}
// Show notification
function showNotification(message, type = "info") {
// Create notification element if it doesn't exist
let notification = document.getElementById("notification");
if (!notification) {
notification = document.createElement("div");
notification.id = "notification";
notification.className = "fixed bottom-4 right-4 p-4 rounded-lg shadow-lg transform transition-opacity duration-300 opacity-0";
document.body.appendChild(notification);
}
// Set notification type
notification.className = notification.className.replace(/bg-\w+-\d+/g, "");
switch (type) {
case "success":
notification.classList.add("bg-green-500", "text-white");
break;
case "error":
notification.classList.add("bg-red-500", "text-white");
break;
case "warning":
notification.classList.add("bg-yellow-500", "text-white");
break;
default:
notification.classList.add("bg-blue-500", "text-white");
}
// Set message
notification.textContent = message;
// Show notification
notification.classList.replace("opacity-0", "opacity-100");
// Hide notification after 3 seconds
setTimeout(() => {
notification.classList.replace("opacity-100", "opacity-0");
}, 3000);
}
// Toggle loading indicator
function toggleLoading(show, section) {
const loadingIndicators = {
"source": document.getElementById("sourceLoadingIndicator"),
"target": document.getElementById("targetLoadingIndicator"),
"analysis": document.getElementById("analysisLoadingIndicator")
};
const indicator = loadingIndicators[section];
if (indicator) {
if (show) {
indicator.classList.remove("hidden");
} else {
indicator.classList.add("hidden");
}
}
}
// Helper functions for displaying error types and severity
function getErrorTypeLabel(errorType) {
const labels = {
"grammar": "خطأ نحوي",
"spelling": "خطأ إملائي",
"meaning": "اختلاف في المعنى",
"missing": "نص مفقود",
"addition": "إضافة غير ضرورية",
"terminology": "مصطلح غير دقيق"
};
return labels[errorType] || errorType;
}
function getSeverityLabel(severity) {
const labels = {
"low": "منخفض",
"medium": "متوسط",
"high": "مرتفع"
};
return labels[severity] || severity;
}
function getSeverityColor(severity) {
const colors = {
"low": "yellow",
"medium": "orange",
"high": "red"
};
return colors[severity] || "gray";
}