joermd commited on
Commit
48b50f8
·
verified ·
1 Parent(s): 52b6aeb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +127 -323
index.html CHANGED
@@ -4,9 +4,8 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>نظام المقارنة والتحليل المتقدم - شركة موندو لينجوا</title>
7
- <!-- استيراد مكتبات Tailwind وFont Awesome وMammoth -->
8
  <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
11
  <style>
12
  /* ================================
@@ -35,14 +34,14 @@
35
  تنسيقات النص والتظليل
36
  ================================= */
37
  .text-comparison { line-height: 1.8; white-space: pre-wrap; }
38
- /* اختلافات بسيطة (نوع MINOR) */
39
  .highlight-minor {
40
  background-color: #FFF9C4; /* أصفر فاتح */
41
  padding: 0 4px;
42
  border-radius: 3px;
43
  font-weight: bold;
44
  }
45
- /* جمل مفقودة بالكامل (نوع MISSING) */
46
  .highlight-missing {
47
  background-color: #FECACA; /* أحمر فاتح */
48
  color: #B91C1C;
@@ -50,7 +49,7 @@
50
  border-radius: 3px;
51
  font-style: italic;
52
  }
53
- /* جمل ناقصة (نوع PARTIAL) */
54
  .highlight-partial {
55
  background-color: #FFCDD2; /* أحمر باهت */
56
  color: #B91C1C;
@@ -58,9 +57,9 @@
58
  border-radius: 3px;
59
  font-weight: bold;
60
  }
61
- /* تظليل الاختلافات الأخرى (أرقام، تواريخ، اختلاف معاني، علامات شك) */
62
  .highlight-number {
63
- background-color: #FDE68A; /* أصفر */
64
  padding: 0 4px;
65
  border-radius: 3px;
66
  font-weight: bold;
@@ -72,7 +71,7 @@
72
  font-weight: bold;
73
  }
74
  .highlight-doubt {
75
- background-color: #DBEAFE; /* أزرق */
76
  color: #1E3A8A;
77
  padding: 0 4px;
78
  border-radius: 3px;
@@ -113,9 +112,7 @@
113
  /* ================================
114
  شاشة الأخطاء والنتائج
115
  ================================= */
116
- #errorsList div {
117
- margin-bottom: 0.5rem;
118
- }
119
  </style>
120
  </head>
121
  <body class="bg-gradient-to-br from-gray-100 via-blue-100 to-indigo-100 min-h-screen">
@@ -127,7 +124,7 @@
127
  <i class="fas fa-chart-line icon"></i> نظام المقارنة والتحليل المتقدم
128
  </h1>
129
  <p class="text-xl opacity-90">
130
- <i class="fas fa-info-circle icon"></i> استخراج كافة المشاكل مع التركيز على النصوص المفقودة في المصدر
131
  </p>
132
  </div>
133
  </header>
@@ -249,251 +246,100 @@
249
  </div>
250
 
251
  <!-- ================================
252
- جافا سكريبت: الوظائف والتحليل
253
  ================================= -->
254
  <script>
255
- // إعدادات الـ API والبرومبت مع تعليمات استخدام العلامات الجديدة
256
- const API_URL = 'https://api.deepseek.com/chat/completions';
257
- const API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01';
258
- const ANALYSIS_PROMPT = `أنت خبير لغوي وتقني متخصص في مراجعة الترجمة التقنية وتحليل النصوص بدقة عالية.
259
- مهمتك مقارنة النص المصدر والنص الهدف واستخراج كافة المشاكل وفقاً للأنواع التالية:
260
- 1. إذا كانت الجملة موجودة في النصين ولكن بها اختلاف بسيط (مثال: اختلاف كلمة أو تاريخ أو معنى بسيط) فاستخدم العلامة [MINOR] ... [/MINOR].
261
- 2. إذا كانت الجملة مختلفة تماماً (أي بها 3 اختلافات أو أكثر) فتعتبر مفقودة في النص الهدف، فاستخدم العلامة [MISSING] ... [/MISSING].
262
- 3. إذا كانت الجملة موجودة في النص المصدر ولكنها ناقصة أو بها كلمات مفقودة في النص الهدف، فاستخدم العلامة [PARTIAL] ... [/PARTIAL].
263
- يُرجى إظهار العلامات في النص المصدر لتبيان مكان الخطأ.
264
-
265
- النص المصدر:
266
- {source}
267
-
268
- النص الهدف:
269
- {target}`;
270
-
271
- // دوال مساعدة للتعامل مع النصوص
272
- function countWords(text) {
273
- return text.trim().split(/\s+/).filter(word => word !== "").length;
274
  }
275
- function escapeRegExp(string) {
276
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
 
 
 
 
 
 
 
 
277
  }
278
- function splitIntoLines(text) {
279
- return text.split(/\n+/).map((line, i) => {
280
- line = line.trim();
281
- if(line && !line.endsWith('.')) {
282
- line += '.';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  }
284
- return `<div class="line-item"><span class="line-number">${i+1}:</span> <span class="line-text">${line}</span></div>`;
285
- }).join('');
286
- }
287
- function getLineNumber(text, substring) {
288
- const index = text.indexOf(substring);
289
- if (index === -1) return "غير محدد";
290
- return text.substring(0, index).split("\n").length;
291
  }
292
 
293
- // دالة تطبيق التظليل على النص المصدر باستخدام العلامات الجديدة
294
- function applyHighlights(originalText, analysisOutput) {
295
- let highlightedText = originalText;
296
- let match;
297
- // اختلاف بسيط [MINOR]
298
- const minorRegex = /\[MINOR\](.*?)\[\/MINOR\]/g;
299
- while ((match = minorRegex.exec(analysisOutput)) !== null) {
300
- const phrase = match[1].trim();
301
- if (phrase) {
302
- const replacement = `<span class="highlight-minor">${phrase}</span>`;
303
- const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
304
- highlightedText = highlightedText.replace(phraseRegex, replacement);
305
- }
306
- }
307
- // جملة مفقودة [MISSING]
308
- const missingRegex = /\[MISSING\](.*?)\[\/MISSING\]/g;
309
- while ((match = missingRegex.exec(analysisOutput)) !== null) {
310
- const phrase = match[1].trim();
311
- if (phrase) {
312
- const replacement = `<span class="highlight-missing">${phrase}</span>`;
313
- const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
314
- highlightedText = highlightedText.replace(phraseRegex, replacement);
315
- }
316
- }
317
- // جملة ناقصة [PARTIAL]
318
- const partialRegex = /\[PARTIAL\](.*?)\[\/PARTIAL\]/g;
319
- while ((match = partialRegex.exec(analysisOutput)) !== null) {
320
- const phrase = match[1].trim();
321
- if (phrase) {
322
- const replacement = `<span class="highlight-partial">${phrase}</span>`;
323
- const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
324
- highlightedText = highlightedText.replace(phraseRegex, replacement);
325
- }
326
- }
327
- // يمكن الإبقاء على التظليل السابق للأرقام والتواريخ واختلاف المعنى وعلامات الشك إن وجدت
328
- const numberRegex = /<([^<>]+)>/g;
329
- while ((match = numberRegex.exec(analysisOutput)) !== null) {
330
- const phrase = match[1].trim();
331
- if (phrase) {
332
- const replacement = `<span class="highlight-number">${phrase}</span>`;
333
- const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
334
- highlightedText = highlightedText.replace(phraseRegex, replacement);
335
- }
336
- }
337
- const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g;
338
- while ((match = meaningRegex.exec(analysisOutput)) !== null) {
339
- const phrase = match[1].trim();
340
- if (phrase) {
341
- const replacement = `<span class="highlight-meaning">${phrase}</span>`;
342
- const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
343
- highlightedText = highlightedText.replace(phraseRegex, replacement);
344
  }
345
- }
346
- const doubtRegex = /\[DOUBT\](.*?)\[\/DOUBT\]/g;
347
- while ((match = doubtRegex.exec(analysisOutput)) !== null) {
348
- const phrase = match[1].trim();
349
- if (phrase) {
350
- const replacement = `<span class="highlight-doubt">${phrase}</span>`;
351
- const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
352
- highlightedText = highlightedText.replace(phraseRegex, replacement);
353
- }
354
- }
355
- return highlightedText;
356
  }
357
 
358
- // دالة توليد شرح تفصيلي للاختلافات باستخدام العلامات الجديدة مع أيقونات توضيحية
359
- function generateExplanation(sourceText, analysisOutput) {
360
- let steps = [];
361
- let match;
362
  const iconMinor = `<i class="fas fa-edit text-orange-500 mr-1"></i>`;
363
  const iconMissing = `<i class="fas fa-exclamation-triangle text-red-500 mr-1"></i>`;
364
  const iconPartial = `<i class="fas fa-minus-circle text-red-500 mr-1"></i>`;
365
- const iconNumber = `<i class="fas fa-hashtag text-yellow-600 mr-1"></i>`;
366
- const iconMeaning = `<i class="fas fa-info-circle text-blue-600 mr-1"></i>`;
367
- const iconDoubt = `<i class="fas fa-question-circle text-indigo-600 mr-1"></i>`;
368
 
369
- // استخراج اختلاف بسيط [MINOR]
370
- const minorRegex2 = /\[MINOR\](.*?)\[\/MINOR\]/g;
371
- while ((match = minorRegex2.exec(analysisOutput)) !== null) {
372
- const phrase = match[1].trim();
373
- if(phrase) {
374
- const lineNum = getLineNumber(sourceText, phrase);
375
- steps.push(`<li>${iconMinor} في السطر ${lineNum}: اختلاف بسيط في الجملة <span class="highlight-minor">${phrase}</span>.</li>`);
376
- }
377
- }
378
- // استخراج جملة مفقودة [MISSING]
379
- const missingRegex2 = /\[MISSING\](.*?)\[\/MISSING\]/g;
380
- while ((match = missingRegex2.exec(analysisOutput)) !== null) {
381
- const phrase = match[1].trim();
382
- if(phrase) {
383
- const lineNum = getLineNumber(sourceText, phrase);
384
- steps.push(`<li>${iconMissing} في السطر ${lineNum}: جملة مفقودة <span class="highlight-missing">${phrase}</span> غير موجودة في النص الهدف.</li>`);
385
- }
386
- }
387
- // استخراج جملة ناقصة [PARTIAL]
388
- const partialRegex2 = /\[PARTIAL\](.*?)\[\/PARTIAL\]/g;
389
- while ((match = partialRegex2.exec(analysisOutput)) !== null) {
390
- const phrase = match[1].trim();
391
- if(phrase) {
392
- const lineNum = getLineNumber(sourceText, phrase);
393
- steps.push(`<li>${iconPartial} في السطر ${lineNum}: جملة ناقصة أو بها كلمات مفقودة <span class="highlight-partial">${phrase}</span> في النص الهدف.</li>`);
394
- }
395
- }
396
- // استخراج اختلافات الأرقام/التواريخ إن وجدت
397
- const numberRegex2 = /<([^<>]+)>/g;
398
- while ((match = numberRegex2.exec(analysisOutput)) !== null) {
399
- const phrase = match[1].trim();
400
- if (phrase) {
401
- const lineNum = getLineNumber(sourceText, phrase);
402
- steps.push(`<li>${iconNumber} في السطر ${lineNum}: الرقم/التاريخ <span class="highlight-number">${phrase}</span> لا يتطابق بين المصدر والهدف.</li>`);
403
  }
404
- }
405
- // استخراج اختلافات المعنى إن وجدت
406
- const meaningRegex2 = /\[MEANING\](.*?)\[\/MEANING\]/g;
407
- while ((match = meaningRegex2.exec(analysisOutput)) !== null) {
408
- const phrase = match[1].trim();
409
- if (phrase) {
410
- const lineNum = getLineNumber(sourceText, phrase);
411
- steps.push(`<li>${iconMeaning} في السطر ${lineNum}: اختلاف في المعنى مع التعبير <span class="highlight-meaning">${phrase}</span>.</li>`);
412
- }
413
- }
414
- // استخراج علامات الشك أو الأخطاء البسيطة إن وجدت
415
- const doubtRegex2 = /\[DOUBT\](.*?)\[\/DOUBT\]/g;
416
- while ((match = doubtRegex2.exec(analysisOutput)) !== null) {
417
- const phrase = match[1].trim();
418
- if (phrase) {
419
- const lineNum = getLineNumber(sourceText, phrase);
420
- steps.push(`<li>${iconDoubt} في السطر ${lineNum}: علامة شك أو خطأ بسيط <span class="highlight-doubt">${phrase}</span> تحتاج مراجعة.</li>`);
421
- }
422
- }
423
- if (steps.length === 0) {
424
  return `<p class="text-green-700"><i class="fas fa-check-circle mr-2"></i> لا توجد اختلافات ملحوظة بين النصين.</p>`;
425
  }
426
- return `<ol class="list-decimal ml-6 space-y-2">${steps.join('')}</ol>`;
427
  }
428
 
429
- // دالة معالجة الملفات (PDF و DOCX)
430
- async function processFile(file) {
431
- let text = "";
432
- if (file.type === 'application/pdf') {
433
- const form = new FormData();
434
- form.append('image', file);
435
- const response = await fetch('https://demo.api4ai.cloud/ocr/v1/results', {
436
- method: 'POST',
437
- body: form,
438
- headers: { 'A4A-CLIENT-APP-ID': 'sample' }
439
- });
440
- const data = await response.json();
441
- text = data.results[0].entities[0].objects[0].entities[0].text;
442
- } else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
443
- const arrayBuffer = await file.arrayBuffer();
444
- const result = await mammoth.extractRawText({ arrayBuffer });
445
- text = result.value;
446
- } else {
447
- throw new Error('نوع الملف غير مدعوم');
448
- }
449
- return text;
450
  }
451
 
452
- // رفع الملفات وإدخال النصوص
453
- document.getElementById('sourceFile')?.addEventListener('change', async (event) => {
454
- const file = event.target.files[0];
455
- if (!file) return;
456
- document.getElementById('processStatus').classList.remove('hidden');
457
- try {
458
- const text = await processFile(file);
459
- document.getElementById('sourceText').value = text;
460
- } catch (error) {
461
- console.error('Error processing source file:', error);
462
- addError('خطأ في معالجة ملف المصدر');
463
- } finally {
464
- document.getElementById('processStatus').classList.add('hidden');
465
- }
466
- });
467
- document.getElementById('targetFile')?.addEventListener('change', async (event) => {
468
- const file = event.target.files[0];
469
- if (!file) return;
470
- document.getElementById('processStatus').classList.remove('hidden');
471
- try {
472
- const text = await processFile(file);
473
- document.getElementById('targetText').value = text;
474
- } catch (error) {
475
- console.error('Error processing target file:', error);
476
- addError('خطأ في معالجة ملف الهدف');
477
- } finally {
478
- document.getElementById('processStatus').classList.add('hidden');
479
- }
480
- });
481
- document.getElementById('sourceExtraFile')?.addEventListener('change', async (event) => {
482
- const file = event.target.files[0];
483
- if (!file) return;
484
- document.getElementById('processStatus').classList.remove('hidden');
485
- try {
486
- const text = await processFile(file);
487
- document.getElementById('sourceExtraText').value = text;
488
- } catch (error) {
489
- console.error('Error processing extra source file:', error);
490
- addError('خطأ في معالجة ملف المصدر الإضافي');
491
- } finally {
492
- document.getElementById('processStatus').classList.add('hidden');
493
- }
494
- });
495
-
496
- // دالة عرض الأخطاء والرسائل
497
  function addError(message, type = 'error') {
498
  const errorsList = document.getElementById('errorsList');
499
  if (!errorsList) return;
@@ -506,8 +352,8 @@
506
  errorsList.appendChild(errorDiv);
507
  }
508
 
509
- // معالجة عملية التحليل عند الضغط على زر "تحليل ومراجعة النصوص"
510
- document.getElementById('submitBtn').addEventListener('click', async () => {
511
  const sourceText = document.getElementById('sourceText').value;
512
  const targetText = document.getElementById('targetText').value;
513
  document.getElementById('errorsList').innerHTML = '';
@@ -519,95 +365,53 @@
519
  return;
520
  }
521
 
522
- const sourceWordCount = countWords(sourceText);
523
- const targetWordCount = countWords(targetText);
 
524
  if (sourceWordCount !== targetWordCount) {
525
  addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف عن النص الهدف (${targetWordCount})`, 'warning');
526
  }
527
 
528
- try {
529
- const progressDiv = document.createElement('div');
530
- progressDiv.className = "bg-indigo-100 p-4 rounded-xl mb-4";
531
- progressDiv.innerHTML = `<div class="flex items-center">
532
- <div class="animate-spin h-6 w-6 border-4 border-indigo-600 rounded-full border-t-transparent mr-3"></div>
533
- <span>جارٍ التحليل...</span>
534
- </div>`;
535
- document.getElementById('errorsList').appendChild(progressDiv);
536
-
537
- const prompt = ANALYSIS_PROMPT
538
- .replace("{source}", sourceText)
539
- .replace("{target}", targetText);
540
-
541
- const payload = {
542
- model: "deepseek-chat",
543
- messages: [
544
- { role: "system", content: "أنت خبير في تحليل ومراجعة النصوص بدقة عالية." },
545
- { role: "user", content: prompt }
546
- ],
547
- temperature: 0.3,
548
- max_tokens: 2048,
549
- stream: false
550
- };
551
- const response = await fetch(API_URL, {
552
- method: 'POST',
553
- headers: {
554
- 'Authorization': 'Bearer ' + API_KEY,
555
- 'Content-Type': 'application/json'
556
- },
557
- body: JSON.stringify(payload)
558
- });
559
- if (!response.ok) {
560
- throw new Error('حدث خطأ بالشبكة: ' + response.statusText);
561
- }
562
- const data = await response.json();
563
- const analysisOutput = data.choices[0].message.content.trim();
564
- progressDiv.remove();
565
-
566
- // إذا كانت النصوص متطابقة تماماً
567
- if (analysisOutput.includes('[MATCH]')) {
568
- const checkDiv = document.createElement('div');
569
- checkDiv.className = "p-4 rounded-xl bg-green-50 text-green-700 flex items-center";
570
- checkDiv.innerHTML = `<i class="fas fa-check-circle mr-2"></i> <span>النصوص متطابقة تماماً</span>`;
571
- document.getElementById('errorsList').appendChild(checkDiv);
572
- document.getElementById('sourceTextReview').innerHTML = splitIntoLines(sourceText);
573
- document.getElementById('targetTextReview').innerHTML = splitIntoLines(targetText);
574
- document.getElementById('explanationText').innerHTML = `<p>النصوص متطابقة ولا توجد فروقات يجب الإشارة إليها.</p>`;
575
- } else {
576
- // تطبيق التظليل على النص المصدر فقط
577
- const sourceHighlighted = applyHighlights(sourceText, analysisOutput);
578
- document.getElementById('sourceTextReview').innerHTML = splitIntoLines(sourceHighlighted);
579
- // عرض النص الهدف بدون تظليل
580
- document.getElementById('targetTextReview').innerHTML = splitIntoLines(targetText);
581
- // توليد شرح تفصيلي لكل مشكلة تم استخراجها باستخدام العلامات الجديدة
582
- const explanationHTML = generateExplanation(sourceText, analysisOutput);
583
- document.getElementById('explanationText').innerHTML = explanationHTML;
584
-
585
- // عرض ملخص للإختلافات بشكل عام إن وُجدت
586
- const numDiffCount = (analysisOutput.match(/<([^<>]+)>/g) || []).length;
587
- const minorDiffCount = (analysisOutput.match(/\[MINOR\](.*?)\[\/MINOR\]/g) || []).length;
588
- const missingDiffCount = (analysisOutput.match(/\[MISSING\](.*?)\[\/MISSING\]/g) || []).length;
589
- const partialDiffCount = (analysisOutput.match(/\[PARTIAL\](.*?)\[\/PARTIAL\]/g) || []).length;
590
- const meaningDiffCount = (analysisOutput.match(/\[MEANING\](.*?)\[\/MEANING\]/g) || []).length;
591
- const doubtDiffCount = (analysisOutput.match(/\[DOUBT\](.*?)\[\/DOUBT\]/g) || []).length;
592
-
593
- if (minorDiffCount || missingDiffCount || partialDiffCount || numDiffCount || meaningDiffCount || doubtDiffCount) {
594
- const summaryDiv = document.createElement('div');
595
- summaryDiv.className = "p-4 rounded-xl bg-yellow-50 text-gray-800";
596
- let summaryText = '<div class="font-bold mb-2">ملخص الاختلافات:</div><ul class="list-disc ml-4 space-y-1">';
597
- if (minorDiffCount) summaryText += `<li>اختلاف بسيط: ${minorDiffCount}</li>`;
598
- if (missingDiffCount) summaryText += `<li>جمل مفقودة: ${missingDiffCount}</li>`;
599
- if (partialDiffCount) summaryText += `<li>جمل ناقصة: ${partialDiffCount}</li>`;
600
- if (numDiffCount) summaryText += `<li>اختلاف في الأرقام/التواريخ: ${numDiffCount}</li>`;
601
- if (meaningDiffCount) summaryText += `<li>اختلاف في المعنى: ${meaningDiffCount}</li>`;
602
- if (doubtDiffCount) summaryText += `<li>علامات الشك: ${doubtDiffCount}</li>`;
603
- summaryText += '</ul>';
604
- summaryDiv.innerHTML = summaryText;
605
- document.getElementById('errorsList').appendChild(summaryDiv);
606
- }
607
- }
608
- } catch (error) {
609
- document.getElementById('errorsList').innerHTML = '';
610
- addError(`خطأ في التحليل: ${error.message}`);
611
  }
612
  });
613
  </script>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>نظام المقارنة والتحليل المتقدم - شركة موندو لينجوا</title>
7
+ <!-- استيراد مكتبات Tailwind وFont Awesome -->
8
  <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
 
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
10
  <style>
11
  /* ================================
 
34
  تنسيقات النص والتظليل
35
  ================================= */
36
  .text-comparison { line-height: 1.8; white-space: pre-wrap; }
37
+ /* اختلاف بسيط */
38
  .highlight-minor {
39
  background-color: #FFF9C4; /* أصفر فاتح */
40
  padding: 0 4px;
41
  border-radius: 3px;
42
  font-weight: bold;
43
  }
44
+ /* جملة مفقودة بالكامل */
45
  .highlight-missing {
46
  background-color: #FECACA; /* أحمر فاتح */
47
  color: #B91C1C;
 
49
  border-radius: 3px;
50
  font-style: italic;
51
  }
52
+ /* جملة ناقصة */
53
  .highlight-partial {
54
  background-color: #FFCDD2; /* أحمر باهت */
55
  color: #B91C1C;
 
57
  border-radius: 3px;
58
  font-weight: bold;
59
  }
60
+ /* تظليل إضافي للأرقام والتواريخ واختلاف المعنى وعلامات الشك */
61
  .highlight-number {
62
+ background-color: #FDE68A;
63
  padding: 0 4px;
64
  border-radius: 3px;
65
  font-weight: bold;
 
71
  font-weight: bold;
72
  }
73
  .highlight-doubt {
74
+ background-color: #DBEAFE;
75
  color: #1E3A8A;
76
  padding: 0 4px;
77
  border-radius: 3px;
 
112
  /* ================================
113
  شاشة الأخطاء والنتائج
114
  ================================= */
115
+ #errorsList div { margin-bottom: 0.5rem; }
 
 
116
  </style>
117
  </head>
118
  <body class="bg-gradient-to-br from-gray-100 via-blue-100 to-indigo-100 min-h-screen">
 
124
  <i class="fas fa-chart-line icon"></i> نظام المقارنة والتحليل المتقدم
125
  </h1>
126
  <p class="text-xl opacity-90">
127
+ <i class="fas fa-info-circle icon"></i> استخراج كافة الاختلافات مع التركيز على النصوص المفقودة في المصدر
128
  </p>
129
  </div>
130
  </header>
 
246
  </div>
247
 
248
  <!-- ================================
249
+ جافا سكريبت: التحليل المحلي للمقارنة
250
  ================================= -->
251
  <script>
252
+ // دالة تقسيم النص إلى جمل (مع التأكد من انتهاء كل جملة بنقطة)
253
+ function splitSentences(text) {
254
+ // تقسيم النص بواسطة النقطة (يمكن تحسينها بتقسيم علامات الاستفهام والتعجب)
255
+ return text.split('.').map(s => s.trim()).filter(s => s.length > 0).map(s => s + '.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  }
257
+
258
+ // دالة لحساب درجة التشابه بين جملتين باستخدام تشابه جاكارد (Jaccard similarity)
259
+ function sentenceSimilarity(s1, s2) {
260
+ const words1 = s1.toLowerCase().split(/\s+/);
261
+ const words2 = s2.toLowerCase().split(/\s+/);
262
+ const set1 = new Set(words1);
263
+ const set2 = new Set(words2);
264
+ const intersection = new Set([...set1].filter(x => set2.has(x)));
265
+ const union = new Set([...set1, ...set2]);
266
+ return intersection.size / union.size;
267
  }
268
+
269
+ // دالة مقارنة الجمل بين المصدر والهدف
270
+ function compareSentences(source, target) {
271
+ const sourceSentences = splitSentences(source);
272
+ const targetSentences = splitSentences(target);
273
+ let results = [];
274
+
275
+ sourceSentences.forEach(sentence => {
276
+ let maxSim = 0;
277
+ targetSentences.forEach(tSentence => {
278
+ const sim = sentenceSimilarity(sentence, tSentence);
279
+ if (sim > maxSim) maxSim = sim;
280
+ });
281
+ let type = 'match';
282
+ // تحديد النوع بناءً على درجة التشابه
283
+ if(maxSim < 0.4) {
284
+ type = 'MISSING';
285
+ } else if(maxSim < 0.8) {
286
+ type = 'PARTIAL';
287
+ } else if(maxSim < 0.95) {
288
+ type = 'MINOR';
289
  }
290
+ results.push({ sentence, similarity: maxSim, type });
291
+ });
292
+ return results;
 
 
 
 
293
  }
294
 
295
+ // دالة بناء النص المصدر مع التظليل بناءً على النتائج
296
+ function buildAnnotatedText(results) {
297
+ return results.map((item, i) => {
298
+ let annotated = item.sentence;
299
+ if(item.type === 'MINOR') {
300
+ annotated = `<span class="highlight-minor">${annotated} [MINOR]</span>`;
301
+ } else if(item.type === 'PARTIAL') {
302
+ annotated = `<span class="highlight-partial">${annotated} [PARTIAL]</span>`;
303
+ } else if(item.type === 'MISSING') {
304
+ annotated = `<span class="highlight-missing">${annotated} [MISSING]</span>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  }
306
+ return `<div class="line-item"><span class="line-number">${i+1}:</span> <span class="line-text">${annotated}</span></div>`;
307
+ }).join('');
 
 
 
 
 
 
 
 
 
308
  }
309
 
310
+ // دالة توليد شرح تفصيلي لكل اختلاف
311
+ function generateExplanation(results) {
 
 
312
  const iconMinor = `<i class="fas fa-edit text-orange-500 mr-1"></i>`;
313
  const iconMissing = `<i class="fas fa-exclamation-triangle text-red-500 mr-1"></i>`;
314
  const iconPartial = `<i class="fas fa-minus-circle text-red-500 mr-1"></i>`;
 
 
 
315
 
316
+ let explanations = results.map((item, i) => {
317
+ if(item.type === 'match') return "";
318
+ let explanation = "";
319
+ if(item.type === 'MINOR') {
320
+ explanation = `${iconMinor} السطر ${i+1}: اختلاف بسيط (درجة التشابه: ${(item.similarity*100).toFixed(1)}%)`;
321
+ } else if(item.type === 'PARTIAL') {
322
+ explanation = `${iconPartial} السطر ${i+1}: الجملة ناقصة أو بها كلمات مفقودة (درجة التشابه: ${(item.similarity*100).toFixed(1)}%)`;
323
+ } else if(item.type === 'MISSING') {
324
+ explanation = `${iconMissing} السطر ${i+1}: الجملة مفقودة بالكامل في النص ال��دف (درجة التشابه: ${(item.similarity*100).toFixed(1)}%)`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  }
326
+ return `<li>${explanation}</li>`;
327
+ }).filter(exp => exp !== "");
328
+
329
+ if(explanations.length === 0) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  return `<p class="text-green-700"><i class="fas fa-check-circle mr-2"></i> لا توجد اختلافات ملحوظة بين النصين.</p>`;
331
  }
332
+ return `<ol class="list-decimal ml-6 space-y-2">${explanations.join('')}</ol>`;
333
  }
334
 
335
+ // دالة عرض النص الهدف مع تقسيم الجمل (بدون تظليل)
336
+ function buildTargetText(text) {
337
+ return splitSentences(text).map((sentence, i) => {
338
+ return `<div class="line-item"><span class="line-number">${i+1}:</span> <span class="line-text">${sentence}</span></div>`;
339
+ }).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
341
 
342
+ // دالة عرض الأخطاء والرسائل (نفس الدالة السابقة)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  function addError(message, type = 'error') {
344
  const errorsList = document.getElementById('errorsList');
345
  if (!errorsList) return;
 
352
  errorsList.appendChild(errorDiv);
353
  }
354
 
355
+ // عند الضغط على زر التحليل يتم إجراء المقارنة وعرض النتائج
356
+ document.getElementById('submitBtn').addEventListener('click', () => {
357
  const sourceText = document.getElementById('sourceText').value;
358
  const targetText = document.getElementById('targetText').value;
359
  document.getElementById('errorsList').innerHTML = '';
 
365
  return;
366
  }
367
 
368
+ // تنبيه إذا اختلف عدد الكلمات (تحذير بسيط)
369
+ const sourceWordCount = sourceText.trim().split(/\s+/).filter(w => w).length;
370
+ const targetWordCount = targetText.trim().split(/\s+/).filter(w => w).length;
371
  if (sourceWordCount !== targetWordCount) {
372
  addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف عن النص الهدف (${targetWordCount})`, 'warning');
373
  }
374
 
375
+ // إظهار مؤشر التحليل
376
+ const progressDiv = document.createElement('div');
377
+ progressDiv.className = "bg-indigo-100 p-4 rounded-xl mb-4";
378
+ progressDiv.innerHTML = `<div class="flex items-center">
379
+ <div class="animate-spin h-6 w-6 border-4 border-indigo-600 rounded-full border-t-transparent mr-3"></div>
380
+ <span>جارٍ التحليل...</span>
381
+ </div>`;
382
+ document.getElementById('errorsList').appendChild(progressDiv);
383
+
384
+ // إجراء مقارنة الجمل
385
+ const results = compareSentences(sourceText, targetText);
386
+
387
+ // بناء النص المصدر المُعلّم
388
+ const annotatedSource = buildAnnotatedText(results);
389
+ document.getElementById('sourceTextReview').innerHTML = annotatedSource;
390
+
391
+ // عرض النص الهدف كما هو مع تقسيم الجمل
392
+ document.getElementById('targetTextReview').innerHTML = buildTargetText(targetText);
393
+
394
+ // بناء الشرح التفصيلي للاختلافات
395
+ const explanationHTML = generateExplanation(results);
396
+ document.getElementById('explanationText').innerHTML = explanationHTML;
397
+
398
+ // إزالة مؤشر التحليل
399
+ progressDiv.remove();
400
+
401
+ // عرض ملخص الاختلافات إن وُجدت
402
+ const minorCount = results.filter(r => r.type === 'MINOR').length;
403
+ const partialCount = results.filter(r => r.type === 'PARTIAL').length;
404
+ const missingCount = results.filter(r => r.type === 'MISSING').length;
405
+ if(minorCount || partialCount || missingCount) {
406
+ const summaryDiv = document.createElement('div');
407
+ summaryDiv.className = "p-4 rounded-xl bg-yellow-50 text-gray-800";
408
+ let summaryText = '<div class="font-bold mb-2">ملخص الاختلافات:</div><ul class="list-disc ml-4 space-y-1">';
409
+ if(minorCount) summaryText += `<li>اختلاف بسيط: ${minorCount}</li>`;
410
+ if(partialCount) summaryText += `<li>جمل ناقصة: ${partialCount}</li>`;
411
+ if(missingCount) summaryText += `<li>جمل مفقودة: ${missingCount}</li>`;
412
+ summaryText += '</ul>';
413
+ summaryDiv.innerHTML = summaryText;
414
+ document.getElementById('errorsList').appendChild(summaryDiv);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
  });
417
  </script>