joermd commited on
Commit
5a6ee0d
·
verified ·
1 Parent(s): 7929297

Update ocr.html

Browse files
Files changed (1) hide show
  1. ocr.html +439 -526
ocr.html CHANGED
@@ -3,600 +3,513 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>موندو لينجوا - نظام OCR</title>
7
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
 
9
  <style>
10
- body {
11
- font-family: Arial, sans-serif;
12
- margin: 0;
13
- padding: 20px;
14
- background-color: #f8f9fa;
15
  }
16
- .header {
17
- background-color: #3b82f6;
18
- color: white;
19
- padding: 15px;
20
- border-radius: 10px;
21
- margin-bottom: 20px;
22
- text-align: center;
23
  }
24
- .container {
25
- max-width: 800px;
26
- margin: 0 auto;
 
 
 
 
 
 
 
27
  }
28
- .card {
29
- background-color: white;
30
- border-radius: 10px;
31
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
32
- padding: 20px;
33
- margin-bottom: 20px;
34
  }
35
- .pdf-grid {
36
- display: grid;
37
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
38
- gap: 10px;
39
- margin-top: 15px;
40
- }
41
- .pdf-page {
42
- border: 1px solid #ddd;
43
- border-radius: 5px;
44
- padding: 5px;
45
- position: relative;
46
- cursor: pointer;
47
- transition: all 0.2s;
48
- }
49
- .pdf-page:hover {
50
- transform: translateY(-3px);
51
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
52
  }
53
- .pdf-page.selected {
54
- border: 2px solid #3b82f6;
 
 
 
55
  }
56
- .pdf-page img {
57
- width: 100%;
58
- height: auto;
59
- border-radius: 3px;
60
  }
61
- .page-number {
62
- position: absolute;
63
- bottom: 0;
64
- left: 0;
65
- right: 0;
66
- background-color: rgba(0,0,0,0.6);
67
- color: white;
68
- text-align: center;
69
- font-size: 12px;
70
- padding: 2px;
71
  }
72
- .btn-primary {
73
- background-color: #3b82f6;
74
- border-color: #3b82f6;
 
 
75
  }
76
- .btn-success {
77
- background-color: #10b981;
78
- border-color: #10b981;
79
  }
80
- .result-text {
81
- max-height: 300px;
82
- overflow-y: auto;
83
- white-space: pre-wrap;
84
- direction: rtl;
85
- border: 1px solid #ddd;
86
- padding: 10px;
87
- border-radius: 5px;
88
- background-color: #f8f9fa;
89
  }
90
- .spinner-border {
91
- width: 1.5rem;
92
- height: 1.5rem;
93
- margin-left: 0.5rem;
94
  }
95
- .logo {
96
- font-weight: bold;
97
- font-size: 24px;
98
- display: inline-block;
99
- margin-bottom: 5px;
100
  }
101
- .logo span {
102
- color: #bfdbfe;
 
 
103
  }
104
  </style>
105
  </head>
106
- <body>
107
- <div class="container">
108
- <div class="header">
109
- <div class="logo">مـوندو <span>لينجـوا</span></div>
110
- <h1>نظام التعرف الضوئي على النصوص</h1>
111
- <p class="mb-0">استخراج النصوص من الصور والملفات متعددة الصفحات</p>
112
- </div>
113
-
114
- <!-- بطاقة تحميل الملف -->
115
- <div class="card">
116
- <h3>تحميل الملف</h3>
117
- <div class="mb-3">
118
- <input type="file" class="form-control" id="fileInput" accept=".pdf,.jpg,.jpeg,.png">
119
- <div class="form-text">يمكنك تحميل ملف PDF (حتى 30 صفحة) أو صورة</div>
120
  </div>
121
- <button id="processBtn" class="btn btn-primary">معالجة الملف</button>
122
- </div>
123
 
124
- <!-- عرض حالة المعالجة -->
125
- <div id="processingStatus" class="card d-none">
126
- <h3>حالة المعالجة</h3>
127
- <div class="alert alert-info">
128
- <div class="d-flex align-items-center">
129
- <div class="spinner-border text-primary" role="status"></div>
130
- <span id="statusText" class="ms-2">جاري معالجة الملف...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  </div>
132
  </div>
133
- <div class="progress mt-2">
134
- <div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%"></div>
135
- </div>
136
- </div>
137
 
138
- <!-- عرض صفحات PDF -->
139
- <div id="pdfPagesCard" class="card d-none">
140
- <h3>صفحات الملف</h3>
141
- <p>اختر الصفحات التي تريد معالجتها (انقر للتحديد)</p>
142
-
143
- <div class="mb-3">
144
- <button id="selectAllBtn" class="btn btn-sm btn-outline-primary me-2">تحديد الكل</button>
145
- <button id="deselectAllBtn" class="btn btn-sm btn-outline-secondary">إلغاء تحديد الكل</button>
146
- </div>
147
-
148
- <div id="pdfPagesContainer" class="pdf-grid"></div>
149
-
150
- <div class="mt-3">
151
- <button id="extractImagesBtn" class="btn btn-success me-2">تحويل إلى صور</button>
152
- <button id="extractTextBtn" class="btn btn-primary">استخراج النص</button>
 
 
 
153
  </div>
154
- </div>
155
 
156
- <!-- عرض النتائج -->
157
- <div id="resultsCard" class="card d-none">
158
- <h3>النص المستخرج</h3>
159
- <div id="resultText" class="result-text mt-3">
160
- لم يتم استخراج نص بعد.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  </div>
162
- <div class="mt-3">
163
- <button id="copyTextBtn" class="btn btn-outline-primary me-2">نسخ النص</button>
164
- <button id="downloadTextBtn" class="btn btn-success">تنزيل النص</button>
 
 
 
 
165
  </div>
166
- </div>
167
  </div>
168
 
 
169
  <script>
170
- // تهيئة PDF.js
171
- pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
172
-
173
- // إعدادات API4AI OCR
174
- const RAPIDAPI_KEY = 'eb11693cddmshb8bd157e05b74acp1f6aa4jsn4369fa546e55';
175
- const OCR_API_URL = 'https://ocr43.p.rapidapi.com/v1/results';
176
-
177
- // متغيرات عامة
178
- let documentPages = [];
179
- let selectedPages = [];
180
- let extractedTexts = [];
181
-
182
- // عناصر DOM
183
- const fileInput = document.getElementById('fileInput');
184
- const processBtn = document.getElementById('processBtn');
185
- const processingStatus = document.getElementById('processingStatus');
186
- const statusText = document.getElementById('statusText');
187
- const progressBar = document.getElementById('progressBar');
188
- const pdfPagesCard = document.getElementById('pdfPagesCard');
189
- const pdfPagesContainer = document.getElementById('pdfPagesContainer');
190
- const selectAllBtn = document.getElementById('selectAllBtn');
191
- const deselectAllBtn = document.getElementById('deselectAllBtn');
192
- const extractImagesBtn = document.getElementById('extractImagesBtn');
193
- const extractTextBtn = document.getElementById('extractTextBtn');
194
- const resultsCard = document.getElementById('resultsCard');
195
- const resultText = document.getElementById('resultText');
196
- const copyTextBtn = document.getElementById('copyTextBtn');
197
- const downloadTextBtn = document.getElementById('downloadTextBtn');
198
-
199
- // إضافة مستمعات الأحداث
200
- document.addEventListener('DOMContentLoaded', function() {
201
- processBtn.addEventListener('click', processFile);
202
- selectAllBtn.addEventListener('click', selectAllPages);
203
- deselectAllBtn.addEventListener('click', deselectAllPages);
204
- extractImagesBtn.addEventListener('click', extractImages);
205
- extractTextBtn.addEventListener('click', extractText);
206
- copyTextBtn.addEventListener('click', copyText);
207
- downloadTextBtn.addEventListener('click', downloadText);
208
- });
209
-
210
- // معالجة الملف
211
- async function processFile() {
212
- if (!fileInput.files || fileInput.files.length === 0) {
213
- alert('الرجاء اختيار ملف أولاً');
214
- return;
215
- }
216
 
217
- const file = fileInput.files[0];
 
 
218
 
219
- // إظهار حالة المعالجة
220
- processingStatus.classList.remove('d-none');
221
- statusText.textContent = 'جاري معالجة الملف...';
222
- progressBar.style.width = '0%';
223
 
224
- // إخفاء بطاقات أخرى
225
- pdfPagesCard.classList.add('d-none');
226
- resultsCard.classList.add('d-none');
227
 
228
- // إفراغ مصفوفات الصفحات
229
- documentPages = [];
230
- selectedPages = [];
231
- extractedTexts = [];
232
 
233
- try {
234
- const fileType = file.name.split('.').pop().toLowerCase();
235
-
236
- if (fileType === 'pdf') {
237
- await processPdf(file);
238
- } else if (['jpg', 'jpeg', 'png'].includes(fileType)) {
239
- await processImage(file);
240
- } else {
241
- throw new Error('نوع الملف غير مدعوم. يرجى اختيار ملف PDF أو صورة.');
242
- }
243
-
244
- // إظهار بطاقة صفحات PDF
245
- pdfPagesCard.classList.remove('d-none');
246
-
247
- // إخفاء حالة المعالجة
248
- processingStatus.classList.add('d-none');
249
-
250
- } catch (error) {
251
- console.error('Error processing file:', error);
252
- statusText.textContent = `خطأ: ${error.message}`;
253
- // لا نخفي حالة المعالجة لإظهار الخطأ
254
- }
 
 
 
 
255
  }
256
-
257
- // معالجة ملف PDF
258
- async function processPdf(file) {
259
- try {
260
- // تحميل ملف PDF
261
- const arrayBuffer = await file.arrayBuffer();
262
- const pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
263
-
264
- // التحقق من عدد الصفحات
265
- const numPages = pdfDoc.numPages;
266
- if (numPages > 30) {
267
- alert('هذا الملف يحتوي على أكثر من 30 صفحة. سيتم معالجة أول 30 صفحة فقط.');
268
- }
269
-
270
- // مسح حاوية الصفحات
271
- pdfPagesContainer.innerHTML = '';
272
-
273
- // عرض أول 30 صفحة كصور مصغرة
274
- const maxPages = Math.min(numPages, 30);
275
- for (let i = 1; i <= maxPages; i++) {
276
- // تحديث شريط التقدم
277
- progressBar.style.width = `${(i / maxPages) * 100}%`;
278
- statusText.textContent = `جاري معالجة الصفحة ${i} من ${maxPages}...`;
279
-
280
- // تحويل الصفحة إلى صورة
281
- const pageImage = await convertPdfPageToImage(pdfDoc, i);
282
-
283
- // إضافة الصفحة إلى المصفوفة
284
- documentPages.push({
285
- pageNumber: i,
286
- imageData: pageImage.imageData,
287
- width: pageImage.width,
288
- height: pageImage.height
289
- });
290
-
291
- // إنشاء عنصر للصفحة في واجهة المستخدم
292
- createPageElement(pageImage.imageData, i);
293
  }
294
-
295
- } catch (error) {
296
- console.error('Error processing PDF:', error);
297
- throw error;
298
  }
299
- }
300
-
301
- // معالجة ملف صورة
302
- async function processImage(file) {
303
- try {
304
- // قراءة الصورة كـ Data URL
305
- const imageData = await readFileAsDataURL(file);
306
-
307
- // إنشاء صورة لقياس أبعادها
308
- const img = new Image();
309
- await new Promise((resolve, reject) => {
310
- img.onload = resolve;
311
- img.onerror = reject;
312
- img.src = imageData;
313
- });
314
-
315
- // مسح حاوية الصفحات
316
- pdfPagesContainer.innerHTML = '';
317
-
318
- // إضافة الصورة إلى المصفوفة
319
- documentPages = [{
320
- pageNumber: 1,
321
- imageData: imageData,
322
- width: img.width,
323
- height: img.height
324
- }];
325
-
326
- // إنشاء عنصر للصورة في واجهة المستخدم
327
- createPageElement(imageData, 1);
328
-
329
- // تحديث شريط التقدم
330
- progressBar.style.width = '100%';
331
- statusText.textContent = 'تمت معالجة الصورة بنجاح';
332
-
333
- } catch (error) {
334
- console.error('Error processing image:', error);
335
- throw error;
336
  }
337
- }
338
-
339
- // تحويل صفحة PDF إلى صورة
340
- async function convertPdfPageToImage(pdfDoc, pageNumber, scale = 1.5) {
341
- try {
342
- // الحصول على الصفحة من مستند PDF
343
- const page = await pdfDoc.getPage(pageNumber);
344
-
345
- // إنشاء عنصر canvas لرسم الصفحة
346
- const canvas = document.createElement('canvas');
347
- const context = canvas.getContext('2d');
348
-
349
- // ضب�� حجم الـ canvas بناءً على حجم الصفحة ومقياس التكبير
350
- const viewport = page.getViewport({ scale });
351
- canvas.width = viewport.width;
352
- canvas.height = viewport.height;
353
-
354
- // رسم الصفحة على الـ canvas
355
- await page.render({
356
- canvasContext: context,
357
- viewport: viewport
358
- }).promise;
359
-
360
- // تحويل الـ canvas إلى صورة بصيغة PNG
361
- return {
362
- imageData: canvas.toDataURL('image/png'),
363
- width: viewport.width,
364
- height: viewport.height,
365
- pageNumber: pageNumber
366
- };
367
- } catch (error) {
368
- console.error(`Error converting PDF page ${pageNumber} to image:`, error);
369
- throw error;
370
  }
 
371
  }
372
-
373
- // إنشاء عنصر للصفحة في واجهة المستخدم
374
- function createPageElement(imageData, pageNumber) {
375
- const pageDiv = document.createElement('div');
376
- pageDiv.className = 'pdf-page';
377
- pageDiv.dataset.page = pageNumber;
378
-
379
- const img = document.createElement('img');
380
- img.src = imageData;
381
- img.alt = `صفحة ${pageNumber}`;
382
-
383
- const pageNumberDiv = document.createElement('div');
384
- pageNumberDiv.className = 'page-number';
385
- pageNumberDiv.textContent = `صفحة ${pageNumber}`;
386
-
387
- pageDiv.appendChild(img);
388
- pageDiv.appendChild(pageNumberDiv);
389
-
390
- // إضافة حدث النقر للتحديد
391
- pageDiv.addEventListener('click', function() {
392
- this.classList.toggle('selected');
393
-
394
- // تحديث مصفوفة الصفحات المحددة
395
- const page = parseInt(this.dataset.page);
396
- if (this.classList.contains('selected')) {
397
- if (!selectedPages.includes(page)) {
398
- selectedPages.push(page);
399
- }
400
- } else {
401
- const index = selectedPages.indexOf(page);
402
- if (index > -1) {
403
- selectedPages.splice(index, 1);
404
- }
405
- }
406
  });
407
-
408
- pdfPagesContainer.appendChild(pageDiv);
409
  }
410
-
411
- // قراءة ملف كـ Data URL
412
- function readFileAsDataURL(file) {
413
- return new Promise((resolve, reject) => {
414
- const reader = new FileReader();
415
- reader.onload = e => resolve(e.target.result);
416
- reader.onerror = reject;
417
- reader.readAsDataURL(file);
418
- });
419
  }
420
-
421
- // تحديد كل الصفحات
422
- function selectAllPages() {
423
- document.querySelectorAll('.pdf-page').forEach(page => {
424
- page.classList.add('selected');
425
- const pageNumber = parseInt(page.dataset.page);
426
- if (!selectedPages.includes(pageNumber)) {
427
- selectedPages.push(pageNumber);
 
 
 
 
428
  }
429
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  }
431
-
432
- // إلغاء تحديد كل الصفحات
433
- function deselectAllPages() {
434
- document.querySelectorAll('.pdf-page').forEach(page => {
435
- page.classList.remove('selected');
436
- });
437
- selectedPages = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  }
439
-
440
- // استخراج الصور
441
- function extractImages() {
442
- if (documentPages.length === 0) {
443
- alert('لا توجد صفحات للمعالجة');
444
- return;
 
 
 
 
 
 
 
 
445
  }
446
-
447
- // اختيار الصفحات المحددة فقط أو كل الصفحات إذا لم يتم تحديد أي صفحة
448
- const pagesToExtract = selectedPages.length > 0
449
- ? documentPages.filter(page => selectedPages.includes(page.pageNumber))
450
- : documentPages;
451
-
452
- if (pagesToExtract.length === 0) {
453
- alert('الرجاء تحديد صفحة واحدة على الأقل');
454
- return;
 
 
 
 
 
 
455
  }
456
-
457
- // إنشاء وتنزيل كل صورة
458
- pagesToExtract.forEach(page => {
459
- const a = document.createElement('a');
460
- a.href = page.imageData;
461
- a.download = `mondo_lingua_page_${page.pageNumber}.png`;
462
- document.body.appendChild(a);
463
- a.click();
464
- document.body.removeChild(a);
465
- });
466
-
467
- // إظهار رسالة نجاح
468
- alert(`تم استخراج ${pagesToExtract.length} صورة بنجاح`);
469
  }
470
-
471
- // استخراج النص
472
- async function extractText() {
473
- if (documentPages.length === 0) {
474
- alert('لا توجد صفحات للمعالجة');
475
- return;
476
- }
477
 
478
- // اختيار الصفحات المحددة فقط أو كل الصفحات إذا لم يتم تحديد أي صفحة
479
- const pagesToProcess = selectedPages.length > 0
480
- ? documentPages.filter(page => selectedPages.includes(page.pageNumber))
481
- : documentPages;
482
 
483
- if (pagesToProcess.length === 0) {
484
- alert('الرجاء تحديد صفحة واحدة على الأقل');
485
  return;
486
  }
487
 
488
- // إظهار حالة المعالجة
489
- processingStatus.classList.remove('d-none');
490
- statusText.textContent = 'جاري استخراج النص...';
491
- progressBar.style.width = '0%';
492
-
493
- try {
494
- extractedTexts = [];
495
-
496
- // معالجة كل صفحة
497
- for (let i = 0; i < pagesToProcess.length; i++) {
498
- const page = pagesToProcess[i];
499
-
500
- // تحديث التقدم
501
- progressBar.style.width = `${((i + 1) / pagesToProcess.length) * 100}%`;
502
- statusText.textContent = `جاري معالجة الصفحة ${i + 1} من ${pagesToProcess.length}...`;
503
-
504
- // استخراج النص من الصورة باستخدام OCR API
505
- const pageText = await extractTextFromImage(page.imageData, page.pageNumber);
506
- extractedTexts.push(pageText);
507
- }
508
-
509
- // جمع النصوص المستخرجة
510
- const combinedText = extractedTexts.join('\n\n');
511
-
512
- // عرض النص المستخرج
513
- resultText.textContent = combinedText;
514
- resultsCard.classList.remove('d-none');
515
-
516
- // إخفاء حالة المعالجة
517
- processingStatus.classList.add('d-none');
518
-
519
- // التمرير إلى النتائج
520
- resultsCard.scrollIntoView({ behavior: 'smooth' });
521
-
522
- } catch (error) {
523
- console.error('Error extracting text:', error);
524
- statusText.textContent = `خطأ: ${error.message}`;
525
  }
526
- }
527
-
528
- // استخراج النص من صورة باستخدام API4AI OCR
529
- async function extractTextFromImage(imageData, pageNumber) {
530
  try {
531
- // تحويل Data URL إلى Blob
532
- const response = await fetch(imageData);
533
- const blob = await response.blob();
534
-
535
- // إنشاء FormData وإضافة الصورة
536
- const formData = new FormData();
537
- formData.append('image', blob, `page_${pageNumber}.png`);
 
 
 
 
 
 
538
 
539
- // طلب OCR
540
- const ocrResponse = await fetch(OCR_API_URL, {
 
 
 
 
 
 
 
 
 
 
 
 
541
  method: 'POST',
542
  headers: {
543
- 'X-RapidAPI-Key': RAPIDAPI_KEY,
544
- 'X-RapidAPI-Host': 'ocr43.p.rapidapi.com'
545
  },
546
- body: formData
547
  });
548
 
549
- if (!ocrResponse.ok) {
550
- throw new Error(`فشل في طلب OCR: ${ocrResponse.status}`);
551
  }
552
 
553
- const data = await ocrResponse.json();
 
554
 
555
- try {
556
- // استخراج النص من الاستجابة
557
- const text = data.results[0].entities[0].objects[0].entities[0].text;
558
- return `=== صفحة ${pageNumber} ===\n${text}`;
559
- } catch (e) {
560
- console.error('Error parsing OCR response:', e);
561
- return `=== صفحة ${pageNumber} ===\n[خطأ في معالجة النص]`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  }
 
563
  } catch (error) {
564
- console.error(`Error in OCR for page ${pageNumber}:`, error);
565
- throw error;
566
  }
567
- }
568
-
569
- // نسخ النص
570
- function copyText() {
571
- const text = resultText.textContent;
572
- if (!text || text === 'لم يتم استخراج نص بعد.') {
573
- alert('لا يوجد نص للنسخ');
574
- return;
575
- }
576
-
577
- navigator.clipboard.writeText(text)
578
- .then(() => alert('تم نسخ النص بنجاح'))
579
- .catch(err => alert('حدث خطأ أثناء نسخ النص: ' + err));
580
- }
581
-
582
- // تنزيل النص
583
- function downloadText() {
584
- const text = resultText.textContent;
585
- if (!text || text === 'لم يتم استخراج نص بعد.') {
586
- alert('لا يوجد نص للتنزيل');
587
- return;
588
- }
589
-
590
- const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
591
- const url = URL.createObjectURL(blob);
592
- const a = document.createElement('a');
593
- a.href = url;
594
- a.download = 'mondo_lingua_ocr_text.txt';
595
- document.body.appendChild(a);
596
- a.click();
597
- document.body.removeChild(a);
598
- URL.revokeObjectURL(url);
599
- }
600
  </script>
601
  </body>
602
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>نظام المقارنة والترجمة الخاص بشركة موندو لينجوا</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
10
  <style>
11
+ @keyframes gradient {
12
+ 0% { background-position: 0% 50%; }
13
+ 50% { background-position: 100% 50%; }
14
+ 100% { background-position: 0% 50%; }
 
15
  }
16
+ .animate-gradient {
17
+ background-size: 200% 200%;
18
+ animation: gradient 15s ease infinite;
 
 
 
 
19
  }
20
+ .transition-all { transition: all 0.3s ease-in-out; }
21
+ .animate-scale { transition: transform 0.2s ease-in-out; }
22
+ .animate-scale:hover { transform: scale(1.02); }
23
+ .file-drop-active { border-color: #3B82F6; background-color: rgba(59,130,246,0.1); }
24
+ .text-comparison { line-height: 1.8; white-space: pre-wrap; }
25
+ /* تمييز اختلاف الأرقام */
26
+ .highlight-number {
27
+ background-color: #FDE68A;
28
+ padding: 0 2px;
29
+ border-radius: 2px;
30
  }
31
+ /* تمييز النصوص الناقصة */
32
+ .highlight-missing {
33
+ background-color: #BFDBFE;
34
+ padding: 0 2px;
35
+ border-radius: 2px;
 
36
  }
37
+ /* تمييز اختلاف المعنى */
38
+ .highlight-meaning {
39
+ color: #EF4444;
40
+ font-weight: bold;
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
+ /* تقسيم شاشة المعاينة إلى جزئين */
43
+ .split-view {
44
+ display: grid;
45
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
46
+ gap: 1rem;
47
  }
48
+ .result-section {
49
+ border-top: 3px solid #E0E7FF;
50
+ margin-top: 2rem;
51
+ padding-top: 2rem;
52
  }
53
+ /* تنسيق كل سطر مع رقم السطر */
54
+ .line-item {
55
+ display: flex;
56
+ align-items: flex-start;
57
+ margin-bottom: 0.5rem;
 
 
 
 
 
58
  }
59
+ .line-number {
60
+ width: 30px;
61
+ font-weight: bold;
62
+ color: #4B5563;
63
+ flex-shrink: 0;
64
  }
65
+ .line-text {
66
+ flex: 1;
 
67
  }
68
+ /* تحسينات إضافية للواجهة */
69
+ .card-hover {
70
+ transition: all 0.3s ease;
 
 
 
 
 
 
71
  }
72
+ .card-hover:hover {
73
+ box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 10px 10px -5px rgba(59, 130, 246, 0.04);
74
+ transform: translateY(-2px);
 
75
  }
76
+ .pulse-animation {
77
+ animation: pulse 2s infinite;
 
 
 
78
  }
79
+ @keyframes pulse {
80
+ 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); }
81
+ 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
82
+ 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
83
  }
84
  </style>
85
  </head>
86
+ <body class="bg-gradient-to-br from-gray-50 via-blue-50 to-indigo-50 min-h-screen">
87
+ <div class="min-h-screen pb-12">
88
+ <!-- Header -->
89
+ <header class="bg-gradient-to-r from-blue-600 via-indigo-600 to-purple-600 animate-gradient text-white py-10 mb-10 shadow-xl">
90
+ <div class="max-w-6xl mx-auto px-4">
91
+ <h1 class="text-5xl font-bold text-center mb-4 animate-scale">نظام شركة موندو لينجوا</h1>
92
+ <p class="text-center text-xl text-blue-100 opacity-90">مقارنة وتحليل النصوص – المصدر مرجع أساسي</p>
 
 
 
 
 
 
 
93
  </div>
94
+ </header>
 
95
 
96
+ <!-- Main Content -->
97
+ <main class="max-w-6xl mx-auto px-4">
98
+ <!-- قسم رفع الملفات -->
99
+ <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">
100
+ <h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3">
101
+ <i class="fas fa-file-upload text-blue-600 ml-2"></i> تحميل الملفات
102
+ </h2>
103
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
104
+ <!-- ملف السورس -->
105
+ <div class="group border-2 border-dashed border-blue-200 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100">
106
+ <label class="cursor-pointer block">
107
+ <input type="file" id="sourceFile" accept=".docx,.pdf" class="hidden">
108
+ <i class="fas fa-file-upload text-5xl text-blue-500 mb-4"></i>
109
+ <span class="text-lg text-blue-600 group-hover:text-blue-700">ملف السورس</span>
110
+ </label>
111
+ </div>
112
+ <!-- ملف التارجت -->
113
+ <div class="group border-2 border-dashed border-purple-200 rounded-xl p-8 text-center hover:border-purple-500 transition-colors duration-300 bg-purple-50 hover:bg-purple-100">
114
+ <label class="cursor-pointer block">
115
+ <input type="file" id="targetFile" accept=".docx,.pdf" class="hidden">
116
+ <i class="fas fa-file-download text-5xl text-purple-500 mb-4"></i>
117
+ <span class="text-lg text-purple-600 group-hover:text-purple-700">ملف التارجت</span>
118
+ </label>
119
+ </div>
120
+ </div>
121
+ <div id="processStatus" class="hidden mt-4">
122
+ <div class="flex items-center justify-center space-x-3 bg-blue-100 rounded-xl p-4">
123
+ <div class="animate-spin h-8 w-8 border-4 border-blue-600 rounded-full border-t-transparent"></div>
124
+ <span class="text-lg text-blue-700">جارٍ معالجة الملف...</span>
125
+ </div>
126
  </div>
127
  </div>
 
 
 
 
128
 
129
+ <!-- إدخال النصوص يدويًا -->
130
+ <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">
131
+ <div class="space-y-6">
132
+ <!-- ال��ص المصدر -->
133
+ <div class="group">
134
+ <label class="block text-lg font-bold text-gray-700 mb-3 flex items-center">
135
+ <i class="fas fa-language text-blue-600 ml-2"></i> النص المصدر
136
+ </label>
137
+ <textarea id="sourceText" dir="rtl" class="w-full px-6 py-4 border-2 border-gray-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب النص المصدر هنا..."></textarea>
138
+ </div>
139
+ <!-- النص الهدف -->
140
+ <div class="group">
141
+ <label class="block text-lg font-bold text-gray-700 mb-3 flex items-center">
142
+ <i class="fas fa-language text-purple-600 ml-2"></i> النص الهدف
143
+ </label>
144
+ <textarea id="targetText" dir="ltr" class="w-full px-6 py-4 border-2 border-gray-200 rounded-xl focus:ring-purple-200 focus:border-purple-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب النص الهدف هنا..."></textarea>
145
+ </div>
146
+ </div>
147
  </div>
 
148
 
149
+ <!-- زر التحليل -->
150
+ <button id="submitBtn" class="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-bold py-5 px-8 rounded-xl transition-all transform hover:scale-105 focus:ring-blue-200 text-xl shadow-lg hover:shadow-xl mb-8 pulse-animation">
151
+ <div class="flex items-center justify-center">
152
+ <i class="fas fa-sync-alt ml-2"></i> تحليل النصوص
153
+ </div>
154
+ </button>
155
+
156
+ <!-- شاشة نتائج التحليل (المعاينة المقسمة) -->
157
+ <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">
158
+ <h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-3 flex items-center">
159
+ <i class="fas fa-search text-green-600 ml-2"></i> نتائج التحليل والمقارنة
160
+ </h2>
161
+ <!-- هنا يتم عرض الأخطاء والاختلافات فقط -->
162
+ <div id="errorsList" class="space-y-3 mb-6"></div>
163
+ <div class="result-section split-view">
164
+ <!-- عرض النص المصدر بعد التعليم مع تقسيمه إلى أسطر -->
165
+ <div>
166
+ <h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center">
167
+ <i class="fas fa-file-alt text-blue-600 ml-2"></i> النص المصدر (مع التعليم)
168
+ </h4>
169
+ <div id="sourceTextReview" class="bg-blue-50 rounded-xl p-6 min-h-[200px] border-2 border-blue-100 text-comparison"></div>
170
+ </div>
171
+ <!-- عرض النص الهدف بعد التعليم مع تقسيمه إلى أسطر -->
172
+ <div>
173
+ <h4 class="text-lg font-bold text-gray-700 mb-3 flex items-center">
174
+ <i class="fas fa-file-alt text-purple-600 ml-2"></i> النص الهدف (مع التعليم)
175
+ </h4>
176
+ <div id="targetTextReview" class="bg-gray-50 rounded-xl p-6 min-h-[200px] border-2 border-gray-200 text-comparison"></div>
177
+ </div>
178
+ </div>
179
  </div>
180
+
181
+ <!-- صندوق شرح الاختلافات -->
182
+ <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">
183
+ <h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-3 flex items-center">
184
+ <i class="fas fa-info-circle text-green-600 ml-2"></i> شرح الاختلافات
185
+ </h2>
186
+ <div id="explanationText" class="text-lg text-gray-700"></div>
187
  </div>
188
+ </main>
189
  </div>
190
 
191
+ <!-- JavaScript -->
192
  <script>
193
+ // API URL ومفتاح API (تم التحديث لاستخدام DeepSeek API)
194
+ const API_URL = 'https://api.deepseek.com/chat/completions';
195
+ const API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01';
196
+
197
+ /*
198
+ الـ prompt المحسن:
199
+ أنت خبير في تحليل النصوص ومقارنتها. قم بمقارنة النص المصدر والنص الهدف بدقة عالية واكتشف الاختلافات التالية فقط:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ 1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي "<" و ">"
202
+ 2. النصوص المفقودة: إذا كان هناك نص موجود في المصدر ومفقود في الهدف، ضع النص المفقود بين "__" و "__"
203
+ 3. اختلافات المعنى: إذا كانت هناك كلمة أو عبارة تختلف في المعنى، ضعها بين [MEANING] و [/MEANING]
204
 
205
+ إذا كانت النصوص متطابقة تماماً، اكتب [MATCH] فقط.
 
 
 
206
 
207
+ اعتبر النص المصدر هو المرجع الأساسي للمقارنة.
 
 
208
 
209
+ النص المصدر:
210
+ {source}
 
 
211
 
212
+ النص الهدف:
213
+ {target}
214
+
215
+ أخرج فقط الاختلافات المحددة بالعلامات المذكورة أعلاه، دون أي شرح إضافي.
216
+ */
217
+ const ANALYSIS_PROMPT = `أنت خبير في تحليل النصوص ومقارنتها. قم بمقارنة النص المصدر والنص الهدف بدقة عالية واكتشف الاختلافات التالية فقط:
218
+
219
+ 1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي "<" و ">"
220
+ 2. النصوص المفقودة: إذا كان هناك نص موجود في المصدر ومفقود في الهدف، ضع النص المفقود بين "__" و "__"
221
+ 3. اختلافات المعنى: إذا كانت هناك كلمة أو عبارة تختلف في المعنى، ضعها بين [MEANING] و [/MEANING]
222
+
223
+ إذا كانت النصوص متطابقة تماماً، اكتب [MATCH] فقط.
224
+
225
+ اعتبر النص المصدر هو المرجع الأساسي للمقارنة.
226
+
227
+ النص المصدر:
228
+ {source}
229
+
230
+ النص الهدف:
231
+ {target}
232
+
233
+ أخرج فقط الاختلافات المحددة بالعلامات المذكورة أعلاه، دون أي شرح إضافي.`;
234
+
235
+ // دالة لحساب عدد الكلمات
236
+ function countWords(text) {
237
+ return text.trim().split(/\s+/).filter(word => word !== "").length;
238
  }
239
+
240
+ // دالة لمساعدة في الهروب من أحرف regex الخاصة
241
+ function escapeRegExp(string) {
242
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
243
+ }
244
+
245
+ // دالة تطبيق التمييز على النص باستخدام العلامات في نتيجة التحليل
246
+ function applyHighlights(originalText, analysisOutput) {
247
+ let highlightedText = originalText;
248
+ // تمييز اختلافات الأرقام
249
+ const numberRegex = /<([^<>]+)>/g;
250
+ let match;
251
+ while ((match = numberRegex.exec(analysisOutput)) !== null) {
252
+ const phrase = match[1].trim();
253
+ if (phrase) {
254
+ const replacement = `<span class="highlight-number">${phrase}</span>`;
255
+ const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
256
+ highlightedText = highlightedText.replace(phraseRegex, replacement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  }
 
 
 
 
258
  }
259
+ // تمييز النصوص الناقصة
260
+ const missingRegex = /__(.*?)__/g;
261
+ while ((match = missingRegex.exec(analysisOutput)) !== null) {
262
+ const phrase = match[1].trim();
263
+ if (phrase) {
264
+ const replacement = `<span class="highlight-missing">__${phrase}__</span>`;
265
+ const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
266
+ highlightedText = highlightedText.replace(phraseRegex, replacement);
267
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  }
269
+ // تمييز اختلاف المعنى
270
+ const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g;
271
+ while ((match = meaningRegex.exec(analysisOutput)) !== null) {
272
+ const phrase = match[1].trim();
273
+ if (phrase) {
274
+ const replacement = `<span class="highlight-meaning">${phrase}</span>`;
275
+ const phraseRegex = new RegExp(escapeRegExp(phrase), 'g');
276
+ highlightedText = highlightedText.replace(phraseRegex, replacement);
277
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  }
279
+ return highlightedText;
280
  }
281
+
282
+ // دالة تقسيم النص إلى أسطر مع عرض رقم السطر
283
+ function splitIntoLines(text) {
284
+ const lines = text.split('\n');
285
+ let html = "";
286
+ lines.forEach((line, index) => {
287
+ html += `<div class="line-item"><span class="line-number">${index + 1}:</span> <span class="line-text">${line}</span></div>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  });
289
+ return html;
 
290
  }
291
+
292
+ // دالة للحصول على رقم السطر لظهور عبارة معينة في النص
293
+ function getLineNumber(text, substring) {
294
+ const index = text.indexOf(substring);
295
+ if (index === -1) return "غير محدد";
296
+ return text.substring(0, index).split("\n").length;
 
 
 
297
  }
298
+
299
+ // دالة توليد شرح الاختلافات
300
+ function generateExplanation(sourceText, analysisOutput) {
301
+ let explanation = "";
302
+ // شرح النصوص الناقصة
303
+ const missingRegex = /__(.*?)__/g;
304
+ let match;
305
+ while ((match = missingRegex.exec(analysisOutput)) !== null) {
306
+ const phrase = match[1].trim();
307
+ if (phrase) {
308
+ const lineNum = getLineNumber(sourceText, phrase);
309
+ explanation += `<p>في السطر ${lineNum}: النص "${phrase}" مفقود في النص الهدف.</p>`;
310
  }
311
+ }
312
+ // شرح اختلاف الأرقام
313
+ const numberRegex = /<([^<>]+)>/g;
314
+ while ((match = numberRegex.exec(analysisOutput)) !== null) {
315
+ const phrase = match[1].trim();
316
+ if (phrase) {
317
+ const lineNum = getLineNumber(sourceText, phrase);
318
+ explanation += `<p>في السطر ${lineNum}: اختلاف في الرقم "${phrase}".</p>`;
319
+ }
320
+ }
321
+ // شرح اختلاف المعنى
322
+ const meaningRegex = /\[MEANING\](.*?)\[\/MEANING\]/g;
323
+ while ((match = meaningRegex.exec(analysisOutput)) !== null) {
324
+ const phrase = match[1].trim();
325
+ if (phrase) {
326
+ const lineNum = getLineNumber(sourceText, phrase);
327
+ explanation += `<p>في السطر ${lineNum}: اختلاف في المعنى: "${phrase}".</p>`;
328
+ }
329
+ }
330
+ if (explanation === "") {
331
+ explanation = `<p>[MATCH] النصوص متطابقة تماماً.</p>`;
332
+ }
333
+ return explanation;
334
  }
335
+
336
+ // دالة موحدة لمعالجة الملفات (PDF و DOCX)
337
+ async function processFile(file) {
338
+ let text = "";
339
+ if (file.type === 'application/pdf') {
340
+ const form = new FormData();
341
+ form.append('image', file);
342
+ const response = await fetch('https://demo.api4ai.cloud/ocr/v1/results', {
343
+ method: 'POST',
344
+ body: form,
345
+ headers: { 'A4A-CLIENT-APP-ID': 'sample' }
346
+ });
347
+ const data = await response.json();
348
+ text = data.results[0].entities[0].objects[0].entities[0].text;
349
+ } else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
350
+ const arrayBuffer = await file.arrayBuffer();
351
+ const result = await mammoth.extractRawText({ arrayBuffer });
352
+ text = result.value;
353
+ } else {
354
+ throw new Error('نوع الملف غير مدعوم');
355
+ }
356
+ return text;
357
  }
358
+
359
+ // رفع ملف السورس
360
+ document.getElementById('sourceFile')?.addEventListener('change', async (event) => {
361
+ const file = event.target.files[0];
362
+ if (!file) return;
363
+ document.getElementById('processStatus').classList.remove('hidden');
364
+ try {
365
+ const text = await processFile(file);
366
+ document.getElementById('sourceText').value = text;
367
+ } catch (error) {
368
+ console.error('Error processing source file:', error);
369
+ addError('خطأ في معالجة ملف السورس');
370
+ } finally {
371
+ document.getElementById('processStatus').classList.add('hidden');
372
  }
373
+ });
374
+
375
+ // رفع ملف التارجت
376
+ document.getElementById('targetFile')?.addEventListener('change', async (event) => {
377
+ const file = event.target.files[0];
378
+ if (!file) return;
379
+ document.getElementById('processStatus').classList.remove('hidden');
380
+ try {
381
+ const text = await processFile(file);
382
+ document.getElementById('targetText').value = text;
383
+ } catch (error) {
384
+ console.error('Error processing target file:', error);
385
+ addError('خطأ في معالجة ملف التارجت');
386
+ } finally {
387
+ document.getElementById('processStatus').classList.add('hidden');
388
  }
389
+ });
390
+
391
+ // دالة عرض الأخطاء أو الرسائل
392
+ function addError(message, type = 'error') {
393
+ const errorsList = document.getElementById('errorsList');
394
+ if (!errorsList) return;
395
+ const errorDiv = document.createElement('div');
396
+ errorDiv.className = `p-4 rounded-xl ${type === 'error' ? 'bg-red-50 text-red-700' : 'bg-yellow-50 text-yellow-700'}`;
397
+ errorDiv.innerHTML = `<div class="flex items-center">
398
+ <i class="fas fa-${type === 'error' ? 'exclamation-circle' : 'info-circle'} ml-2 text-${type === 'error' ? 'red' : 'yellow'}-500"></i>
399
+ <span>${message}</span>
400
+ </div>`;
401
+ errorsList.appendChild(errorDiv);
402
  }
403
+
404
+ // عند الضغط على زر التحليل
405
+ document.getElementById('submitBtn').addEventListener('click', async () => {
406
+ const sourceText = document.getElementById('sourceText').value;
407
+ const targetText = document.getElementById('targetText').value;
 
 
408
 
409
+ // مسح الرسائل السابقة وإظهار قسم النتائج
410
+ document.getElementById('errorsList').innerHTML = '';
411
+ document.getElementById('resultSection').classList.remove('hidden');
412
+ document.getElementById('explanationBox').classList.remove('hidden');
413
 
414
+ if (!sourceText || !targetText) {
415
+ addError('يرجى إدخال كلا النصين المصدر والهدف');
416
  return;
417
  }
418
 
419
+ // مقارنة عدد الكلمات (تنبيه اختياري)
420
+ const sourceWordCount = countWords(sourceText);
421
+ const targetWordCount = countWords(targetText);
422
+ if (sourceWordCount !== targetWordCount) {
423
+ addError(`عدد كلمات النص المصدر (${sourceWordCount}) يختلف عن النص الهدف (${targetWordCount})`, 'warning');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  }
425
+
 
 
 
426
  try {
427
+ // عرض مؤشر التقدم أثناء انتظار الرد
428
+ const progressDiv = document.createElement('div');
429
+ progressDiv.className = "bg-blue-100 p-4 rounded-xl mb-4";
430
+ progressDiv.innerHTML = `<div class="flex items-center">
431
+ <div class="animate-spin h-6 w-6 border-4 border-blue-600 rounded-full border-t-transparent ml-3"></div>
432
+ <span>جارٍ التحليل...</span>
433
+ </div>`;
434
+ document.getElementById('errorsList').appendChild(progressDiv);
435
+
436
+ // بناء prompt باستخدام النصين المُدخلين
437
+ const prompt = ANALYSIS_PROMPT
438
+ .replace("{source}", sourceText)
439
+ .replace("{target}", targetText);
440
 
441
+ // استخدام DeepSeek API
442
+ const payload = {
443
+ model: "deepseek-chat",
444
+ messages: [
445
+ { role: "system", content: "أنت خبير في تحليل النصوص ومقارنتها بدقة عالية." },
446
+ { role: "user", content: prompt }
447
+ ],
448
+ temperature: 0.3,
449
+ max_tokens: 2048,
450
+ stream: false
451
+ };
452
+
453
+ // استدعاء API التحليل
454
+ const response = await fetch(API_URL, {
455
  method: 'POST',
456
  headers: {
457
+ 'Authorization': 'Bearer ' + API_KEY,
458
+ 'Content-Type': 'application/json'
459
  },
460
+ body: JSON.stringify(payload)
461
  });
462
 
463
+ if (!response.ok) {
464
+ throw new Error('حدث خطأ بالشبكة: ' + response.statusText);
465
  }
466
 
467
+ const data = await response.json();
468
+ const analysisOutput = data.choices[0].message.content.trim();
469
 
470
+ progressDiv.remove();
471
+
472
+ if (analysisOutput.includes('[MATCH]')) {
473
+ const checkDiv = document.createElement('div');
474
+ checkDiv.className = "p-4 rounded-xl bg-green-50 text-green-700 flex items-center";
475
+ checkDiv.innerHTML = `<i class="fas fa-check-circle ml-2 text-lg"></i> <span class="text-lg">النصوص متطابقة تماماً</span>`;
476
+ document.getElementById('errorsList').appendChild(checkDiv);
477
+ document.getElementById('sourceTextReview').innerHTML = splitIntoLines(sourceText);
478
+ document.getElementById('targetTextReview').innerHTML = splitIntoLines(targetText);
479
+ document.getElementById('explanationText').innerHTML = `<p>[MATCH] لا توجد اختلافات.</p>`;
480
+ } else {
481
+ const sourceHighlighted = applyHighlights(sourceText, analysisOutput);
482
+ const targetHighlighted = applyHighlights(targetText, analysisOutput);
483
+ // تقسيم النصوص المُظّلّة إلى أسطر مع رقم السطر
484
+ document.getElementById('sourceTextReview').innerHTML = splitIntoLines(sourceHighlighted);
485
+ document.getElementById('targetTextReview').innerHTML = splitIntoLines(targetHighlighted);
486
+
487
+ // توليد شرح الاختلافات
488
+ const explanationHTML = generateExplanation(sourceText, analysisOutput);
489
+ document.getElementById('explanationText').innerHTML = explanationHTML;
490
+
491
+ // عرض ملخص الاختلافات إن وُجدت
492
+ const numDiffCount = (analysisOutput.match(/<([^<>]+)>/g) || []).length;
493
+ const missingDiffCount = (analysisOutput.match(/__(.*?)__/g) || []).length;
494
+ const meaningDiffCount = (analysisOutput.match(/\[MEANING\](.*?)\[\/MEANING\]/g) || []).length;
495
+ if (numDiffCount > 0 || missingDiffCount > 0 || meaningDiffCount > 0) {
496
+ const summaryDiv = document.createElement('div');
497
+ summaryDiv.className = "p-4 rounded-xl bg-yellow-50 text-gray-800";
498
+ let summaryText = '<div class="font-bold mb-2">ملخص الاختلافات:</div><ul class="list-disc mr-6 space-y-1">';
499
+ if (numDiffCount > 0) summaryText += `<li>اختلاف في الأرقام: ${numDiffCount}</li>`;
500
+ if (missingDiffCount > 0) summaryText += `<li>نصوص مفقودة: ${missingDiffCount}</li>`;
501
+ if (meaningDiffCount > 0) summaryText += `<li>اختلاف في المعنى: ${meaningDiffCount}</li>`;
502
+ summaryText += '</ul>';
503
+ summaryDiv.innerHTML = summaryText;
504
+ document.getElementById('errorsList').appendChild(summaryDiv);
505
+ }
506
  }
507
+
508
  } catch (error) {
509
+ document.getElementById('errorsList').innerHTML = '';
510
+ addError(`خطأ في التحليل: ${error.message}`);
511
  }
512
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  </script>
514
  </body>
515
  </html>