|
|
|
|
|
<html lang="ar" dir="rtl"> |
|
|
<head><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"66aa55c6986dd41ad50db61d"}};</script><script>window.huggingface={variables:{"SPACE_CREATOR_USER_ID":"66aa55c6986dd41ad50db61d"}};</script> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>نظام المقارنة المطور - شركة موندو لينجوا</title> |
|
|
|
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet"> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> |
|
|
<style> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes gradient { |
|
|
0% { background-position: 0% 50%; } |
|
|
50% { background-position: 100% 50%; } |
|
|
100% { background-position: 0% 50%; } |
|
|
} |
|
|
.animate-gradient { |
|
|
background-size: 200% 200%; |
|
|
animation: gradient 15s ease infinite; |
|
|
} |
|
|
.transition-all { transition: all 0.3s ease-in-out; } |
|
|
.animate-scale { transition: transform 0.2s ease-in-out; } |
|
|
.animate-scale:hover { transform: scale(1.02); } |
|
|
.pulse-animation { animation: pulse 2s infinite; } |
|
|
@keyframes pulse { |
|
|
0% { box-shadow: 0 0 0 0 rgba(156,39,176,0.4); } |
|
|
70% { box-shadow: 0 0 0 10px rgba(156,39,176,0); } |
|
|
100% { box-shadow: 0 0 0 0 rgba(156,39,176,0); } |
|
|
} |
|
|
@keyframes highlight-pulse { |
|
|
0% { opacity: 0.7; } |
|
|
50% { opacity: 1; } |
|
|
100% { opacity: 0.7; } |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.text-comparison { |
|
|
line-height: 2; |
|
|
white-space: pre-wrap; |
|
|
padding: 1rem; |
|
|
border-radius: 8px; |
|
|
background-color: #fdfdfd; |
|
|
box-shadow: inset 0 0 3px rgba(0,0,0,0.1); |
|
|
} |
|
|
.highlight-number { |
|
|
background-color: #FDE68A; |
|
|
padding: 0 4px; |
|
|
border-radius: 3px; |
|
|
font-weight: bold; |
|
|
cursor: pointer; |
|
|
position: relative; |
|
|
display: inline-block; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
|
animation: highlight-pulse 3s ease-in-out infinite; |
|
|
border-bottom: 2px solid #F59E0B; |
|
|
} |
|
|
.highlight-missing { |
|
|
background-color: #BFDBFE; |
|
|
padding: 0 4px; |
|
|
border-radius: 3px; |
|
|
font-style: italic; |
|
|
cursor: pointer; |
|
|
position: relative; |
|
|
display: inline-block; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
|
animation: highlight-pulse 3s ease-in-out infinite; |
|
|
border-bottom: 2px solid #3B82F6; |
|
|
} |
|
|
.highlight-meaning { |
|
|
background-color: #fecaca; |
|
|
color: #B91C1C; |
|
|
padding: 0 4px; |
|
|
border-radius: 3px; |
|
|
font-weight: bold; |
|
|
cursor: pointer; |
|
|
position: relative; |
|
|
display: inline-block; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
|
animation: highlight-pulse 3s ease-in-out infinite; |
|
|
border-bottom: 2px solid #EF4444; |
|
|
} |
|
|
|
|
|
|
|
|
.highlight-number::before, |
|
|
.highlight-missing::before, |
|
|
.highlight-meaning::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 8px; |
|
|
height: 8px; |
|
|
border-radius: 50%; |
|
|
right: -10px; |
|
|
top: 50%; |
|
|
transform: translateY(-50%); |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
.highlight-number::before { |
|
|
background-color: #F59E0B; |
|
|
} |
|
|
|
|
|
.highlight-missing::before { |
|
|
background-color: #3B82F6; |
|
|
} |
|
|
|
|
|
.highlight-meaning::before { |
|
|
background-color: #EF4444; |
|
|
} |
|
|
|
|
|
|
|
|
.completely-missing { |
|
|
background-color: #93C5FD; |
|
|
color: #1E3A8A; |
|
|
padding: 2px 8px; |
|
|
border-radius: 4px; |
|
|
margin: 0 2px; |
|
|
font-weight: bold; |
|
|
border-right: 3px solid #2563EB; |
|
|
border-left: 1px solid #2563EB; |
|
|
display: inline-block; |
|
|
position: relative; |
|
|
animation: highlight-pulse 3s ease-in-out infinite; |
|
|
} |
|
|
|
|
|
|
|
|
.partially-missing { |
|
|
background-color: #DBEAFE; |
|
|
color: #1E40AF; |
|
|
padding: 2px 8px; |
|
|
border-radius: 4px; |
|
|
margin: 0 2px; |
|
|
font-style: italic; |
|
|
border-bottom: 2px dashed #3B82F6; |
|
|
display: inline-block; |
|
|
animation: highlight-pulse 3s ease-in-out infinite; |
|
|
} |
|
|
|
|
|
|
|
|
.highlight-number::after { |
|
|
content: "١٢٣"; |
|
|
font-size: 8px; |
|
|
position: absolute; |
|
|
top: -8px; |
|
|
left: 0; |
|
|
background: #F59E0B; |
|
|
color: white; |
|
|
padding: 0 3px; |
|
|
border-radius: 3px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.highlight-missing::after { |
|
|
content: "..."; |
|
|
font-size: 8px; |
|
|
position: absolute; |
|
|
top: -8px; |
|
|
left: 0; |
|
|
background: #3B82F6; |
|
|
color: white; |
|
|
padding: 0 3px; |
|
|
border-radius: 3px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.highlight-meaning::after { |
|
|
content: "!"; |
|
|
font-size: 8px; |
|
|
position: absolute; |
|
|
top: -8px; |
|
|
left: 0; |
|
|
background: #EF4444; |
|
|
color: white; |
|
|
padding: 0 3px; |
|
|
border-radius: 3px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.split-view { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
|
|
gap: 1.5rem; |
|
|
} |
|
|
.line-item { |
|
|
display: flex; |
|
|
align-items: flex-start; |
|
|
margin-bottom: 0.75rem; |
|
|
padding: 0.5rem; |
|
|
border-radius: 4px; |
|
|
transition: background-color 0.2s; |
|
|
} |
|
|
.line-item:hover { |
|
|
background-color: #f1f5f9; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
|
|
transform: translateY(-1px); |
|
|
} |
|
|
.line-number { |
|
|
width: 40px; |
|
|
font-weight: bold; |
|
|
color: #4B5563; |
|
|
flex-shrink: 0; |
|
|
background-color: #e5e7eb; |
|
|
padding: 0 5px; |
|
|
border-radius: 3px; |
|
|
text-align: center; |
|
|
margin-left: 8px; |
|
|
} |
|
|
.line-text { |
|
|
flex: 1; |
|
|
line-height: 1.8; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.card-hover { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.card-hover:hover { |
|
|
box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.1), 0 10px 10px -5px rgba(59, 130, 246, 0.04); |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.collapsible-section { |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 8px; |
|
|
margin-bottom: 12px; |
|
|
overflow: hidden; |
|
|
background-color: #f9fafb; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.collapsible-section:hover { |
|
|
box-shadow: 0 3px 6px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
.section-header { |
|
|
background-color: #f3f4f6; |
|
|
padding: 14px 16px; |
|
|
cursor: pointer; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
font-weight: 600; |
|
|
border-bottom: 1px solid #e5e7eb; |
|
|
transition: background-color 0.2s; |
|
|
} |
|
|
|
|
|
.section-header:hover { |
|
|
background-color: #e5e7eb; |
|
|
} |
|
|
|
|
|
.section-content { |
|
|
max-height: 0; |
|
|
overflow: hidden; |
|
|
transition: max-height 0.3s ease-out; |
|
|
padding: 0 16px; |
|
|
} |
|
|
|
|
|
.section-content.open { |
|
|
max-height: 1000px; |
|
|
padding: 16px; |
|
|
transition: max-height 0.5s ease-in, padding 0.3s ease-in; |
|
|
} |
|
|
|
|
|
.draft-marker { |
|
|
display: inline-block; |
|
|
background-color: #e5e7eb; |
|
|
padding: 2px 6px; |
|
|
border-radius: 4px; |
|
|
font-size: 0.75rem; |
|
|
margin-right: 8px; |
|
|
color: #4b5563; |
|
|
} |
|
|
|
|
|
.sync-word { |
|
|
color: #4f46e5; |
|
|
font-weight: 500; |
|
|
background-color: rgba(79, 70, 229, 0.1); |
|
|
padding: 0 3px; |
|
|
border-radius: 3px; |
|
|
} |
|
|
|
|
|
.paragraph-section { |
|
|
border-left: 3px solid #d1d5db; |
|
|
padding-left: 16px; |
|
|
margin-bottom: 20px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.paragraph-section::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
background-color: #d1d5db; |
|
|
border-radius: 50%; |
|
|
left: -6.5px; |
|
|
top: 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.logo-container { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
|
|
|
.logo { |
|
|
width: 90px; |
|
|
height: 90px; |
|
|
border-radius: 50%; |
|
|
background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2); |
|
|
margin-left: 1.5rem; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.logo::after { |
|
|
content: "ر"; |
|
|
font-size: 48px; |
|
|
font-weight: bold; |
|
|
color: white; |
|
|
text-shadow: 1px 1px 3px rgba(0,0,0,0.3); |
|
|
} |
|
|
|
|
|
.logo::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: radial-gradient(circle at 70% 30%, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 60%); |
|
|
} |
|
|
|
|
|
.company-name { |
|
|
font-size: 2rem; |
|
|
font-weight: bold; |
|
|
color: #fff; |
|
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
|
|
|
|
|
|
.segment-comparison { |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 12px; |
|
|
margin-bottom: 1.5rem; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.segment-comparison:hover { |
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1); |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
.segment-header { |
|
|
background-color: #f3f4f6; |
|
|
padding: 1rem 1.25rem; |
|
|
font-weight: 600; |
|
|
border-bottom: 1px solid #e5e7eb; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.segment-content { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 1px; |
|
|
background-color: #e5e7eb; |
|
|
} |
|
|
|
|
|
.segment-source, .segment-target { |
|
|
background-color: #fff; |
|
|
padding: 1.25rem; |
|
|
transition: background-color 0.2s ease; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.segment-source { |
|
|
background-color: #f0f9ff; |
|
|
border-left: 3px solid #93c5fd; |
|
|
} |
|
|
|
|
|
.segment-target { |
|
|
background-color: #fdf2f8; |
|
|
border-right: 3px solid #fbcfe8; |
|
|
} |
|
|
|
|
|
.segment-source:hover, .segment-target:hover { |
|
|
background-color: #f8fafc; |
|
|
} |
|
|
|
|
|
.segment-source::after, .segment-target::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
height: 3px; |
|
|
width: 0; |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
|
|
|
.segment-source::after { |
|
|
background-color: #3b82f6; |
|
|
right: 0; |
|
|
} |
|
|
|
|
|
.segment-target::after { |
|
|
background-color: #ec4899; |
|
|
left: 0; |
|
|
} |
|
|
|
|
|
.segment-source:hover::after, .segment-target:hover::after { |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
.segment-notes { |
|
|
grid-column: span 2; |
|
|
background-color: #fffbeb; |
|
|
padding: 1rem 1.25rem; |
|
|
border-top: 1px solid #e5e7eb; |
|
|
font-size: 0.95rem; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.segment-notes::before { |
|
|
content: "ملاحظات"; |
|
|
position: absolute; |
|
|
top: -10px; |
|
|
right: 20px; |
|
|
background-color: #fbbf24; |
|
|
color: #fff; |
|
|
padding: 2px 10px; |
|
|
border-radius: 10px; |
|
|
font-size: 12px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.segment-reference { |
|
|
grid-column: span 2; |
|
|
background-color: #f0fdf4; |
|
|
padding: 1rem 1.25rem; |
|
|
border-top: 1px solid #e5e7eb; |
|
|
font-size: 0.95rem; |
|
|
color: #166534; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.segment-reference::before { |
|
|
content: "مرجع"; |
|
|
position: absolute; |
|
|
top: -10px; |
|
|
right: 20px; |
|
|
background-color: #22c55e; |
|
|
color: #fff; |
|
|
padding: 2px 10px; |
|
|
border-radius: 10px; |
|
|
font-size: 12px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.segment-tag { |
|
|
display: inline-block; |
|
|
padding: 0.35rem 0.75rem; |
|
|
border-radius: 9999px; |
|
|
font-size: 0.8rem; |
|
|
font-weight: 500; |
|
|
margin-right: 0.5rem; |
|
|
position: relative; |
|
|
padding-right: 25px; |
|
|
} |
|
|
|
|
|
.segment-tag::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
border-radius: 50%; |
|
|
right: 8px; |
|
|
top: 50%; |
|
|
transform: translateY(-50%); |
|
|
} |
|
|
|
|
|
.tag-error { |
|
|
background-color: #fee2e2; |
|
|
color: #b91c1c; |
|
|
} |
|
|
|
|
|
.tag-error::before { |
|
|
background-color: #ef4444; |
|
|
} |
|
|
|
|
|
.tag-warning { |
|
|
background-color: #fef3c7; |
|
|
color: #92400e; |
|
|
} |
|
|
|
|
|
.tag-warning::before { |
|
|
background-color: #f59e0b; |
|
|
} |
|
|
|
|
|
.tag-info { |
|
|
background-color: #dbeafe; |
|
|
color: #1e40af; |
|
|
} |
|
|
|
|
|
.tag-info::before { |
|
|
background-color: #3b82f6; |
|
|
} |
|
|
|
|
|
.tag-success { |
|
|
background-color: #d1fae5; |
|
|
color: #065f46; |
|
|
} |
|
|
|
|
|
.tag-success::before { |
|
|
background-color: #10b981; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.pdf-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); |
|
|
gap: 12px; |
|
|
margin-top: 20px; |
|
|
} |
|
|
|
|
|
.pdf-page { |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 6px; |
|
|
padding: 8px; |
|
|
position: relative; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.pdf-page:hover { |
|
|
transform: translateY(-4px); |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
|
|
border-color: #bfdbfe; |
|
|
} |
|
|
|
|
|
.pdf-page.selected { |
|
|
border: 2px solid #3b82f6; |
|
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3); |
|
|
} |
|
|
|
|
|
.pdf-page img { |
|
|
width: 100%; |
|
|
height: auto; |
|
|
border-radius: 4px; |
|
|
object-fit: cover; |
|
|
} |
|
|
|
|
|
.page-number { |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
background-color: rgba(0,0,0,0.7); |
|
|
color: white; |
|
|
text-align: center; |
|
|
font-size: 12px; |
|
|
padding: 3px; |
|
|
border-bottom-left-radius: 5px; |
|
|
border-bottom-right-radius: 5px; |
|
|
} |
|
|
|
|
|
|
|
|
.result-text { |
|
|
max-height: 300px; |
|
|
overflow-y: auto; |
|
|
white-space: pre-wrap; |
|
|
direction: rtl; |
|
|
border: 1px solid #ddd; |
|
|
padding: 1rem; |
|
|
border-radius: 8px; |
|
|
background-color: #f8f9fa; |
|
|
font-size: 1.05rem; |
|
|
line-height: 1.8; |
|
|
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.result-text:focus { |
|
|
border-color: #3b82f6; |
|
|
outline: none; |
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3), inset 0 1px 3px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
|
|
|
.page-preview { |
|
|
margin-bottom: 15px; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 8px; |
|
|
padding: 15px; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.page-preview:hover { |
|
|
box-shadow: 0 3px 6px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.page-preview h4 { |
|
|
background-color: #f0f8ff; |
|
|
padding: 8px 12px; |
|
|
border-radius: 6px; |
|
|
margin-bottom: 12px; |
|
|
font-weight: 600; |
|
|
color: #1e40af; |
|
|
border-right: 3px solid #3b82f6; |
|
|
} |
|
|
|
|
|
|
|
|
.progress { |
|
|
height: 0.5rem; |
|
|
border-radius: 9999px; |
|
|
overflow: hidden; |
|
|
background-color: #e5e7eb; |
|
|
} |
|
|
|
|
|
.progress-bar { |
|
|
height: 100%; |
|
|
border-radius: 9999px; |
|
|
background: linear-gradient(90deg, #3b82f6, #2563eb); |
|
|
transition: width 0.4s ease; |
|
|
} |
|
|
|
|
|
.progress-bar.success { |
|
|
background: linear-gradient(90deg, #10b981, #059669); |
|
|
} |
|
|
|
|
|
.progress-bar.warning { |
|
|
background: linear-gradient(90deg, #f59e0b, #d97706); |
|
|
} |
|
|
|
|
|
.progress-bar.error { |
|
|
background: linear-gradient(90deg, #ef4444, #dc2626); |
|
|
} |
|
|
|
|
|
|
|
|
.stats-badge { |
|
|
background: linear-gradient(135deg, #3b82f6, #1e40af); |
|
|
color: white; |
|
|
font-size: 14px; |
|
|
padding: 6px 12px; |
|
|
border-radius: 20px; |
|
|
margin-right: 10px; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
|
transition: all 0.3s; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.stats-badge::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: -50%; |
|
|
width: 150%; |
|
|
height: 100%; |
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); |
|
|
transform: skewX(-25deg); |
|
|
animation: shine 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes shine { |
|
|
0% { left: -50%; } |
|
|
100% { left: 150%; } |
|
|
} |
|
|
|
|
|
.stats-badge:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15); |
|
|
} |
|
|
|
|
|
.stats-container { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
align-items: center; |
|
|
margin-bottom: 20px; |
|
|
gap: 10px; |
|
|
} |
|
|
|
|
|
|
|
|
.bg-white-classic { |
|
|
background-color: #ffffff; |
|
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); |
|
|
border-radius: 8px; |
|
|
} |
|
|
|
|
|
|
|
|
.error-popup { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
max-width: 450px; |
|
|
width: 90%; |
|
|
background-color: white; |
|
|
border-radius: 12px; |
|
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.25); |
|
|
z-index: 1000; |
|
|
overflow: hidden; |
|
|
display: none; |
|
|
transform: translateY(10px); |
|
|
opacity: 0; |
|
|
transition: transform 0.3s ease, opacity 0.3s ease; |
|
|
} |
|
|
|
|
|
.error-popup.show { |
|
|
transform: translateY(0); |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.error-popup-header { |
|
|
background: linear-gradient(135deg, #3b82f6, #1e40af); |
|
|
color: white; |
|
|
padding: 12px 18px; |
|
|
font-weight: bold; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.error-popup-body { |
|
|
padding: 18px; |
|
|
max-height: 300px; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
.error-popup-close { |
|
|
cursor: pointer; |
|
|
background: none; |
|
|
border: none; |
|
|
color: white; |
|
|
font-size: 20px; |
|
|
transition: transform 0.2s; |
|
|
} |
|
|
|
|
|
.error-popup-close:hover { |
|
|
transform: scale(1.2); |
|
|
} |
|
|
|
|
|
|
|
|
.sentence-with-error { |
|
|
border-bottom: 2px dotted #b91c1c; |
|
|
padding-bottom: 3px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.sentence-with-error:hover::after { |
|
|
content: "انقر لرؤية التفاصيل"; |
|
|
position: absolute; |
|
|
bottom: -25px; |
|
|
right: 0; |
|
|
background-color: #fef2f2; |
|
|
color: #b91c1c; |
|
|
padding: 3px 8px; |
|
|
border-radius: 4px; |
|
|
font-size: 12px; |
|
|
z-index: 10; |
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.15); |
|
|
} |
|
|
|
|
|
|
|
|
.explanation-panel { |
|
|
background-color: #f8fafc; |
|
|
border: 1px solid #e2e8f0; |
|
|
border-radius: 12px; |
|
|
padding: 20px; |
|
|
margin-top: 24px; |
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.05); |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.explanation-panel::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: -10px; |
|
|
right: 30px; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
background-color: #f8fafc; |
|
|
border-top: 1px solid #e2e8f0; |
|
|
border-right: 1px solid #e2e8f0; |
|
|
transform: rotate(-45deg); |
|
|
} |
|
|
|
|
|
.explanation-panel h5 { |
|
|
font-weight: 600; |
|
|
margin-bottom: 12px; |
|
|
color: #1e40af; |
|
|
font-size: 1.1rem; |
|
|
border-bottom: 2px solid #dbeafe; |
|
|
padding-bottom: 8px; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.view-container { |
|
|
margin-top: 1.5rem; |
|
|
display: none; |
|
|
animation: fadeIn 0.5s; |
|
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; transform: translateY(10px); } |
|
|
to { opacity: 1; transform: translateY(0); } |
|
|
} |
|
|
|
|
|
.view-container.active { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.view-selector { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
margin-bottom: 1.5rem; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.view-btn { |
|
|
padding: 0.75rem 1.5rem; |
|
|
background-color: #e5e7eb; |
|
|
border-radius: 10px; |
|
|
font-weight: 500; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
white-space: nowrap; |
|
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.view-btn:hover { |
|
|
background-color: #d1d5db; |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 3px 6px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.view-btn.active { |
|
|
background: linear-gradient(135deg, #3b82f6, #2563eb); |
|
|
color: white; |
|
|
box-shadow: 0 3px 8px rgba(59, 130, 246, 0.3); |
|
|
} |
|
|
|
|
|
.view-btn.active::after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 3px; |
|
|
background-color: rgba(255,255,255,0.5); |
|
|
} |
|
|
|
|
|
.view-btn i { |
|
|
margin-left: 10px; |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.error-filters { |
|
|
display: flex; |
|
|
gap: 0.75rem; |
|
|
margin-bottom: 1.5rem; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.error-filter { |
|
|
padding: 0.6rem 1.2rem; |
|
|
border-radius: 10px; |
|
|
font-size: 0.95rem; |
|
|
cursor: pointer; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
transition: all 0.3s; |
|
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05); |
|
|
} |
|
|
|
|
|
.error-filter.active { |
|
|
border-width: 2px; |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 3px 8px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.error-filter i { |
|
|
margin-left: 8px; |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
.filter-all { |
|
|
background-color: #f3f4f6; |
|
|
border: 1px solid #d1d5db; |
|
|
color: #4b5563; |
|
|
} |
|
|
|
|
|
.filter-all.active { |
|
|
background-color: #e5e7eb; |
|
|
border-color: #9ca3af; |
|
|
color: #1f2937; |
|
|
} |
|
|
|
|
|
.filter-numbers { |
|
|
background-color: #fef3c7; |
|
|
border: 1px solid #fcd34d; |
|
|
color: #92400e; |
|
|
} |
|
|
|
|
|
.filter-numbers.active { |
|
|
background-color: #fde68a; |
|
|
border-color: #f59e0b; |
|
|
} |
|
|
|
|
|
.filter-missing { |
|
|
background-color: #dbeafe; |
|
|
border: 1px solid #93c5fd; |
|
|
color: #1e40af; |
|
|
} |
|
|
|
|
|
.filter-missing.active { |
|
|
background-color: #bfdbfe; |
|
|
border-color: #3b82f6; |
|
|
} |
|
|
|
|
|
.filter-meaning { |
|
|
background-color: #fee2e2; |
|
|
border: 1px solid #fca5a5; |
|
|
color: #b91c1c; |
|
|
} |
|
|
|
|
|
.filter-meaning.active { |
|
|
background-color: #fecaca; |
|
|
border-color: #ef4444; |
|
|
} |
|
|
|
|
|
|
|
|
.classic-view-improved .text-comparison { |
|
|
background-color: #fff; |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 12px; |
|
|
padding: 1.5rem; |
|
|
line-height: 2.2; |
|
|
font-size: 1.1rem; |
|
|
transition: all 0.3s ease; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
|
} |
|
|
|
|
|
.classic-view-improved .text-comparison:hover { |
|
|
box-shadow: 0 5px 10px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
.classic-view-improved h4 { |
|
|
position: relative; |
|
|
padding-right: 1.5rem; |
|
|
margin-bottom: 1.2rem; |
|
|
display: inline-block; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
.classic-view-improved h4::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
right: 0; |
|
|
top: 50%; |
|
|
transform: translateY(-50%); |
|
|
width: 0.85rem; |
|
|
height: 0.85rem; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.classic-view-improved .source-title::before { |
|
|
background-color: #3b82f6; |
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); |
|
|
} |
|
|
|
|
|
.classic-view-improved .target-title::before { |
|
|
background-color: #ec4899; |
|
|
box-shadow: 0 0 0 3px rgba(236, 72, 153, 0.2); |
|
|
} |
|
|
|
|
|
|
|
|
.aligned-paragraphs { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 2px; |
|
|
background-color: #e5e7eb; |
|
|
border-radius: 10px; |
|
|
overflow: hidden; |
|
|
margin-bottom: 1.25rem; |
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.05); |
|
|
} |
|
|
|
|
|
.paragraph-ar, .paragraph-en { |
|
|
background-color: #fff; |
|
|
padding: 1.2rem; |
|
|
white-space: pre-wrap; |
|
|
transition: background-color 0.2s; |
|
|
line-height: 1.8; |
|
|
} |
|
|
|
|
|
.paragraph-ar:hover, .paragraph-en:hover { |
|
|
background-color: #f9fafb; |
|
|
} |
|
|
|
|
|
.paragraph-ar { |
|
|
background-color: #f0f9ff; |
|
|
direction: rtl; |
|
|
border-left: 3px solid #bfdbfe; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.paragraph-ar::after { |
|
|
content: "المصدر"; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
right: 0; |
|
|
background-color: #3b82f6; |
|
|
color: white; |
|
|
padding: 2px 8px; |
|
|
border-bottom-left-radius: 6px; |
|
|
font-size: 10px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.paragraph-en { |
|
|
background-color: #fdf2f8; |
|
|
direction: ltr; |
|
|
border-right: 3px solid #fbcfe8; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.paragraph-en::after { |
|
|
content: "الهدف"; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
background-color: #ec4899; |
|
|
color: white; |
|
|
padding: 2px 8px; |
|
|
border-bottom-right-radius: 6px; |
|
|
font-size: 10px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.diff-source-target { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 1.5rem; |
|
|
margin-top: 1.5rem; |
|
|
} |
|
|
|
|
|
.diff-panel { |
|
|
background-color: #fff; |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 12px; |
|
|
padding: 1.25rem; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.diff-panel:hover { |
|
|
box-shadow: 0 5px 10px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
.diff-panel-header { |
|
|
font-weight: 600; |
|
|
margin-bottom: 0.75rem; |
|
|
color: #1e40af; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
border-bottom: 2px solid #dbeafe; |
|
|
padding-bottom: 8px; |
|
|
} |
|
|
|
|
|
.diff-panel-header i { |
|
|
margin-left: 0.75rem; |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
.diff-source { |
|
|
background-color: #f0f9ff; |
|
|
border-color: #bfdbfe; |
|
|
} |
|
|
|
|
|
.diff-target { |
|
|
background-color: #fdf2f8; |
|
|
border-color: #fbcfe8; |
|
|
} |
|
|
|
|
|
.diff-reference { |
|
|
background-color: #f0fdf4; |
|
|
border: 1px solid #86efac; |
|
|
border-radius: 12px; |
|
|
padding: 1.25rem; |
|
|
margin-top: 1.5rem; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.diff-reference:hover { |
|
|
box-shadow: 0 5px 10px rgba(0,0,0,0.08); |
|
|
} |
|
|
|
|
|
.reference-header { |
|
|
font-weight: 600; |
|
|
margin-bottom: 0.75rem; |
|
|
color: #166534; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
border-bottom: 2px solid #bbf7d0; |
|
|
padding-bottom: 8px; |
|
|
} |
|
|
|
|
|
.reference-header i { |
|
|
margin-left: 0.75rem; |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.preliminary-highlight-number { |
|
|
background-color: rgba(253, 224, 71, 0.4); |
|
|
padding: 0 4px; |
|
|
border-radius: 3px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.preliminary-highlight-missing { |
|
|
background-color: rgba(147, 197, 253, 0.4); |
|
|
padding: 0 4px; |
|
|
border-radius: 3px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.preliminary-highlight-meaning { |
|
|
background-color: rgba(252, 165, 165, 0.4); |
|
|
padding: 0 4px; |
|
|
border-radius: 3px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
|
|
|
.analysis-summary { |
|
|
background-color: #fff; |
|
|
border-radius: 12px; |
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
|
|
overflow: hidden; |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
|
|
|
.analysis-summary-header { |
|
|
background: linear-gradient(135deg, #3b82f6, #2563eb); |
|
|
color: white; |
|
|
padding: 16px 20px; |
|
|
font-weight: bold; |
|
|
font-size: 1.1rem; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.analysis-summary-header i { |
|
|
margin-left: 10px; |
|
|
font-size: 1.2rem; |
|
|
} |
|
|
|
|
|
.analysis-summary-body { |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.summary-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
|
|
gap: 16px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.summary-card { |
|
|
background-color: #f9fafb; |
|
|
border-radius: 8px; |
|
|
padding: 16px; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
text-align: center; |
|
|
transition: all 0.3s; |
|
|
border: 1px solid #e5e7eb; |
|
|
} |
|
|
|
|
|
.summary-card:hover { |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.summary-card-header { |
|
|
font-size: 3rem; |
|
|
font-weight: bold; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
|
|
|
.summary-card-title { |
|
|
font-size: 0.9rem; |
|
|
color: #4b5563; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.summary-card-title i { |
|
|
margin-left: 6px; |
|
|
} |
|
|
|
|
|
.error-groups { |
|
|
border-top: 1px solid #e5e7eb; |
|
|
padding-top: 16px; |
|
|
} |
|
|
|
|
|
.error-group { |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.error-group-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
font-weight: bold; |
|
|
margin-bottom: 10px; |
|
|
padding-bottom: 6px; |
|
|
border-bottom: 2px solid #f3f4f6; |
|
|
} |
|
|
|
|
|
.error-group-header i { |
|
|
margin-left: 8px; |
|
|
} |
|
|
|
|
|
.error-item { |
|
|
background-color: #f9fafb; |
|
|
border-radius: 8px; |
|
|
padding: 12px; |
|
|
margin-bottom: 10px; |
|
|
border-right: 3px solid; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.error-item:hover { |
|
|
transform: translateX(-5px); |
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
|
} |
|
|
|
|
|
.error-item-header { |
|
|
font-weight: bold; |
|
|
margin-bottom: 6px; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
} |
|
|
|
|
|
.error-item-body { |
|
|
background-color: white; |
|
|
padding: 8px; |
|
|
border-radius: 4px; |
|
|
border: 1px solid #e5e7eb; |
|
|
} |
|
|
|
|
|
.error-item-footer { |
|
|
margin-top: 8px; |
|
|
font-size: 0.85rem; |
|
|
color: #6b7280; |
|
|
font-style: italic; |
|
|
} |
|
|
|
|
|
.error-number { |
|
|
border-right-color: #f59e0b; |
|
|
} |
|
|
|
|
|
.error-missing { |
|
|
border-right-color: #3b82f6; |
|
|
} |
|
|
|
|
|
.error-meaning { |
|
|
border-right-color: #ef4444; |
|
|
} |
|
|
|
|
|
|
|
|
.copyright { |
|
|
text-align: center; |
|
|
padding: 15px 0; |
|
|
font-size: 0.9rem; |
|
|
color: #6b7280; |
|
|
border-top: 1px solid #e5e7eb; |
|
|
margin-top: 40px; |
|
|
} |
|
|
|
|
|
.copyright a { |
|
|
color: #3b82f6; |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.copyright a:hover { |
|
|
text-decoration: underline; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.file-type-selector { |
|
|
background-color: #f8fafc; |
|
|
border: 1px solid #e2e8f0; |
|
|
border-radius: 12px; |
|
|
padding: 1rem; |
|
|
margin-bottom: 1.5rem; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
|
} |
|
|
|
|
|
.file-type-options { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
margin-top: 0.75rem; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.file-type-option { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
padding: 0.75rem 1rem; |
|
|
border: 2px solid #e5e7eb; |
|
|
border-radius: 8px; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
background-color: #fff; |
|
|
min-width: 150px; |
|
|
} |
|
|
|
|
|
.file-type-option:hover { |
|
|
border-color: #3b82f6; |
|
|
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1); |
|
|
} |
|
|
|
|
|
.file-type-option.selected { |
|
|
border-color: #3b82f6; |
|
|
background-color: #eff6ff; |
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); |
|
|
} |
|
|
|
|
|
.file-type-option input[type="radio"] { |
|
|
width: 16px; |
|
|
height: 16px; |
|
|
margin-left: 8px; |
|
|
accent-color: #3b82f6; |
|
|
} |
|
|
|
|
|
.file-type-option label { |
|
|
cursor: pointer; |
|
|
font-weight: 500; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
} |
|
|
|
|
|
.file-type-option i { |
|
|
font-size: 1.1rem; |
|
|
color: #6b7280; |
|
|
} |
|
|
|
|
|
.file-type-option.selected i { |
|
|
color: #3b82f6; |
|
|
} |
|
|
|
|
|
|
|
|
.correction-status { |
|
|
background-color: #fef3c7; |
|
|
border: 1px solid #fcd34d; |
|
|
border-radius: 8px; |
|
|
padding: 1rem; |
|
|
margin: 1rem 0; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.correction-status.active { |
|
|
display: block; |
|
|
animation: fadeIn 0.5s ease-in-out; |
|
|
} |
|
|
|
|
|
.correction-status h4 { |
|
|
color: #92400e; |
|
|
margin-bottom: 0.5rem; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
} |
|
|
|
|
|
.correction-status p { |
|
|
color: #78350f; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
.correction-progress { |
|
|
background-color: #fbbf24; |
|
|
height: 4px; |
|
|
border-radius: 2px; |
|
|
overflow: hidden; |
|
|
margin-top: 0.5rem; |
|
|
} |
|
|
|
|
|
.correction-progress-bar { |
|
|
background-color: #f59e0b; |
|
|
height: 100%; |
|
|
width: 0%; |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
|
|
|
|
|
|
.correction-preview { |
|
|
background-color: #f0fdf4; |
|
|
border: 1px solid #86efac; |
|
|
border-radius: 8px; |
|
|
padding: 1rem; |
|
|
margin: 1rem 0; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.correction-preview.active { |
|
|
display: block; |
|
|
animation: fadeIn 0.5s ease-in-out; |
|
|
} |
|
|
|
|
|
.correction-preview h4 { |
|
|
color: #166534; |
|
|
margin-bottom: 0.75rem; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
} |
|
|
|
|
|
.correction-changes { |
|
|
background-color: #fff; |
|
|
border: 1px solid #d1d5db; |
|
|
border-radius: 6px; |
|
|
padding: 0.75rem; |
|
|
max-height: 200px; |
|
|
overflow-y: auto; |
|
|
font-family: monospace; |
|
|
font-size: 0.9rem; |
|
|
line-height: 1.4; |
|
|
} |
|
|
|
|
|
.corrected-text { |
|
|
background-color: #dcfce7; |
|
|
color: #166534; |
|
|
padding: 2px 4px; |
|
|
border-radius: 3px; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.original-text { |
|
|
background-color: #fee2e2; |
|
|
color: #b91c1c; |
|
|
padding: 2px 4px; |
|
|
border-radius: 3px; |
|
|
text-decoration: line-through; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.async-loading-indicator { |
|
|
display: none; |
|
|
padding: 15px; |
|
|
border-radius: 10px; |
|
|
background-color: rgba(255, 255, 255, 0.95); |
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); |
|
|
position: fixed; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
z-index: 1000; |
|
|
text-align: center; |
|
|
min-width: 250px; |
|
|
} |
|
|
|
|
|
.async-loading-indicator.active { |
|
|
display: block; |
|
|
animation: fadeIn 0.3s; |
|
|
} |
|
|
|
|
|
.async-spinner { |
|
|
display: inline-block; |
|
|
width: 50px; |
|
|
height: 50px; |
|
|
border: 5px solid #e5e7eb; |
|
|
border-radius: 50%; |
|
|
border-top-color: #3b82f6; |
|
|
animation: spin 1s linear infinite; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
|
|
|
.enhanced-popup { |
|
|
position: fixed; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%) scale(0.9); |
|
|
max-width: 600px; |
|
|
width: 90%; |
|
|
background: white; |
|
|
border-radius: 15px; |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); |
|
|
z-index: 1100; |
|
|
opacity: 0; |
|
|
visibility: hidden; |
|
|
overflow: hidden; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.enhanced-popup.show { |
|
|
opacity: 1; |
|
|
visibility: visible; |
|
|
transform: translate(-50%, -50%) scale(1); |
|
|
} |
|
|
|
|
|
.enhanced-popup-header { |
|
|
background: linear-gradient(135deg, #3b82f6, #1e40af); |
|
|
color: white; |
|
|
padding: 20px; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.enhanced-popup-title { |
|
|
font-size: 1.25rem; |
|
|
font-weight: bold; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.enhanced-popup-title i { |
|
|
margin-left: 10px; |
|
|
font-size: 1.5rem; |
|
|
} |
|
|
|
|
|
.enhanced-popup-close { |
|
|
background: none; |
|
|
border: none; |
|
|
color: white; |
|
|
font-size: 24px; |
|
|
cursor: pointer; |
|
|
transition: transform 0.2s; |
|
|
} |
|
|
|
|
|
.enhanced-popup-close:hover { |
|
|
transform: scale(1.2); |
|
|
} |
|
|
|
|
|
.enhanced-popup-body { |
|
|
padding: 25px; |
|
|
max-height: 70vh; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
.enhanced-popup-footer { |
|
|
padding: 15px 25px; |
|
|
background-color: #f8fafc; |
|
|
border-top: 1px solid #e5e7eb; |
|
|
display: flex; |
|
|
justify-content: flex-end; |
|
|
} |
|
|
|
|
|
.popup-overlay { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background-color: rgba(0, 0, 0, 0.5); |
|
|
z-index: 1099; |
|
|
opacity: 0; |
|
|
visibility: hidden; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.popup-overlay.show { |
|
|
opacity: 1; |
|
|
visibility: visible; |
|
|
} |
|
|
|
|
|
.error-illustration { |
|
|
max-width: 100%; |
|
|
margin: 15px 0; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.error-example { |
|
|
background-color: #f8fafc; |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 8px; |
|
|
padding: 15px; |
|
|
margin: 15px 0; |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
|
|
|
.correct-example { |
|
|
background-color: #f0fdf4; |
|
|
border: 1px solid #86efac; |
|
|
border-radius: 8px; |
|
|
padding: 15px; |
|
|
margin: 15px 0; |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50"> |
|
|
<div class="min-h-screen pb-12"> |
|
|
|
|
|
<header class="bg-gradient-to-r from-blue-600 to-indigo-800 text-white py-10 mb-6 shadow-xl"> |
|
|
<div class="max-w-6xl mx-auto px-4"> |
|
|
|
|
|
<div class="flex items-center justify-center mb-6"> |
|
|
<div class="logo-container flex items-center"> |
|
|
<div class="logo pulse-animation"></div> |
|
|
<div class="company-name text-2xl font-bold">شركة ريحان للترجمة المعتمدة</div> |
|
|
</div> |
|
|
</div> |
|
|
<h1 class="text-4xl sm:text-5xl font-bold text-center mb-4 animate-scale">المراجع الذكي</h1> |
|
|
<p class="text-center text-xl text-blue-100 opacity-90">نظام متكامل لمقارنة وتحليل النصوص المترجمة</p> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<main class="max-w-6xl mx-auto px-4"> |
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-6 card-hover"> |
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3"> |
|
|
<i class="fas fa-file-upload text-blue-600 ml-2"></i> تحميل الملفات |
|
|
</h2> |
|
|
|
|
|
|
|
|
<div class="file-type-selector"> |
|
|
<h3 class="text-lg font-bold text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-cog text-blue-600 ml-2"></i> نوع الملف المُحمّل |
|
|
</h3> |
|
|
<p class="text-sm text-gray-600 mb-3">اختر نوع الملف لتحديد طريقة المعالجة المناسبة</p> |
|
|
<div class="file-type-options"> |
|
|
<div class="file-type-option selected" data-type="normal"> |
|
|
<input type="radio" id="normalFile" name="fileType" value="normal" checked> |
|
|
<label for="normalFile"> |
|
|
<i class="fas fa-file-alt"></i> |
|
|
ملف عادي |
|
|
</label> |
|
|
</div> |
|
|
<div class="file-type-option" data-type="official"> |
|
|
<input type="radio" id="officialFile" name="fileType" value="official"> |
|
|
<label for="officialFile"> |
|
|
<i class="fas fa-certificate"></i> |
|
|
مستند رسمي |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-3 p-3 bg-blue-50 rounded-lg"> |
|
|
<p class="text-sm text-blue-800" id="fileTypeDescription"> |
|
|
<i class="fas fa-info-circle ml-1"></i> |
|
|
الملف العادي سيتم تحليله مباشرة بدون تصحيح مسبق |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="correctionStatus" class="correction-status"> |
|
|
<h4> |
|
|
<i class="fas fa-magic"></i> |
|
|
جاري تصحيح النص الرسمي... |
|
|
</h4> |
|
|
<p id="correctionMessage">جاري تحديد الدولة وتطبيق قواعد التصحيح المناسبة...</p> |
|
|
<div class="correction-progress"> |
|
|
<div id="correctionProgressBar" class="correction-progress-bar"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="correctionPreview" class="correction-preview"> |
|
|
<h4> |
|
|
<i class="fas fa-check-circle"></i> |
|
|
معاينة التصحيحات المطبقة |
|
|
</h4> |
|
|
<div id="correctionChanges" class="correction-changes"> |
|
|
|
|
|
</div> |
|
|
<div class="mt-3 flex justify-between items-center"> |
|
|
<span class="text-sm text-green-600"> |
|
|
<i class="fas fa-info-circle ml-1"></i> |
|
|
تم تصحيح الثوابت مع الحفاظ على تنسيق النص الأصلي |
|
|
</span> |
|
|
<button id="proceedWithCorrectedText" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg text-sm"> |
|
|
متابعة بالنص المُصحح |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="stats-container mb-4"> |
|
|
<div class="stats-badge"> |
|
|
عدد الصفحات المعالجة: <span id="ocrCounter">0</span> |
|
|
</div> |
|
|
<div class="stats-badge"> |
|
|
تاريخ آخر معالجة: <span id="lastOcrDate">-</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
|
|
|
<div class="text-center"> |
|
|
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100"> |
|
|
<label class="cursor-pointer block"> |
|
|
<input type="file" id="sourceFile" accept=".docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls" class="hidden"> |
|
|
<i class="fas fa-file-upload text-5xl text-blue-500 mb-4"></i> |
|
|
<span class="text-lg text-blue-600 group-hover:text-blue-700">ملف السورس</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="text-center"> |
|
|
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100"> |
|
|
<label class="cursor-pointer block"> |
|
|
<input type="file" id="targetFile" accept=".docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls" class="hidden"> |
|
|
<i class="fas fa-file-download text-5xl text-blue-500 mb-4"></i> |
|
|
<span class="text-lg text-blue-600 group-hover:text-blue-700">ملف التارجت</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="processingStatus" class="hidden mt-4"> |
|
|
<div class="alert alert-info bg-blue-50 border border-blue-200 rounded-xl p-4 my-4"> |
|
|
<div class="flex items-center"> |
|
|
<div class="animate-spin h-6 w-6 border-4 border-blue-600 rounded-full border-t-transparent ml-3"></div> |
|
|
<span id="statusText">جاري معالجة الملف...</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="progress mt-2"> |
|
|
<div id="progressBar" class="progress-bar" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="pdfPagesCard" class="hidden mt-6"> |
|
|
<h3 class="text-xl font-bold mb-4 text-gray-800">صفحات الملف</h3> |
|
|
<p>اختر الصفحات التي تريد معالجتها (انقر للتحديد)</p> |
|
|
|
|
|
<div class="mb-3"> |
|
|
<button id="selectAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-3 rounded text-sm ml-2">تحديد الكل</button> |
|
|
<button id="deselectAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-1 px-3 rounded text-sm">إلغاء تحديد الكل</button> |
|
|
</div> |
|
|
|
|
|
<div id="pdfPagesContainer" class="pdf-grid"></div> |
|
|
|
|
|
<div class="mt-4 flex items-center space-x-2"> |
|
|
<div class="edit-toolbar flex flex-wrap gap-2"> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="rotateLeft"> |
|
|
<i class="fas fa-undo ml-1"></i> تدوير لليسار |
|
|
</button> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="rotateRight"> |
|
|
<i class="fas fa-redo ml-1"></i> تدوير لليمين |
|
|
</button> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="flipHorizontal"> |
|
|
<i class="fas fa-arrows-alt-h ml-1"></i> قلب أفقي |
|
|
</button> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="flipVertical"> |
|
|
<i class="fas fa-arrows-alt-v ml-1"></i> قلب عمودي |
|
|
</button> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="cropImage"> |
|
|
<i class="fas fa-crop ml-1"></i> قص الصورة |
|
|
</button> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="resetImage"> |
|
|
<i class="fas fa-sync-alt ml-1"></i> إعادة ضبط |
|
|
</button> |
|
|
<button class="edit-tool px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 transition-all" id="improveContrast"> |
|
|
<i class="fas fa-adjust ml-1"></i> تحسين التباين |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-3"> |
|
|
<button id="extractTextBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
<i class="fas fa-magic ml-2"></i> استخراج النص |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="imageEditor" class="hidden mt-4"> |
|
|
<div class="border border-gray-300 rounded-lg bg-gray-100 p-2"> |
|
|
<canvas id="imageCanvas" class="max-w-full"></canvas> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="resultsCard" class="hidden mt-6"> |
|
|
<h3 class="text-xl font-bold mb-4 text-gray-800">النص المستخرج</h3> |
|
|
<div id="resultPreview"></div> |
|
|
<div id="resultTextContainer" class="mt-4"> |
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
<h4 class="font-bold text-gray-800">النص:</h4> |
|
|
<button id="copyTextBtn" class="bg-blue-100 hover:bg-blue-200 text-blue-800 px-3 py-1 rounded-lg text-sm"> |
|
|
<i class="far fa-copy ml-1"></i> نسخ النص |
|
|
</button> |
|
|
</div> |
|
|
<div id="resultText" class="result-text"> |
|
|
لم يتم استخراج نص بعد. |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-3 flex space-x-2"> |
|
|
<button id="useAsSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
استخدام النص كنص مصدر |
|
|
</button> |
|
|
<button id="useAsTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
استخدام النص كنص هدف |
|
|
</button> |
|
|
<button id="downloadTextBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
تنزيل النص |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="excelPreviewCard" class="hidden mt-6"> |
|
|
<h3 class="text-xl font-bold mb-4 text-gray-800">محتوى ملف Excel</h3> |
|
|
<div class="excel-controls" id="sheetSelectorContainer"> |
|
|
|
|
|
</div> |
|
|
<div class="excel-preview" id="excelContent"> |
|
|
|
|
|
</div> |
|
|
<div class="mt-3 flex space-x-2"> |
|
|
<button id="useExcelAsSourceBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
استخدام المحتوى كنص مصدر |
|
|
</button> |
|
|
<button id="useExcelAsTargetBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
استخدام المحتوى كنص هدف |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-6 card-hover"> |
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3"> |
|
|
<i class="fas fa-pen-alt text-blue-600 ml-2"></i> إدخال النصوص |
|
|
</h2> |
|
|
<div class="space-y-6"> |
|
|
|
|
|
<div class="group"> |
|
|
<label class="block text-lg font-bold text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-language text-blue-600 ml-2"></i> النص المصدر |
|
|
</label> |
|
|
<textarea id="sourceText" dir="rtl" class="w-full px-6 py-4 border-2 border-gray-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب النص المصدر هنا..."></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="group"> |
|
|
<label class="block text-lg font-bold text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-language text-blue-600 ml-2"></i> النص الهدف |
|
|
</label> |
|
|
<textarea id="targetText" dir="ltr" class="w-full px-6 py-4 border-2 border-gray-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب النص الهدف هنا..."></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4 flex items-center bg-blue-50 p-3 rounded-lg"> |
|
|
<input type="checkbox" id="terminologyCheck" class="ml-2 w-5 h-5 text-blue-600 rounded"> |
|
|
<label for="terminologyCheck" class="text-lg text-gray-700">استخدام قاعدة المصطلحات</label> |
|
|
<div class="mr-auto"> |
|
|
<span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">ميزة إضافية</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-6 card-hover"> |
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 flex items-center border-b pb-3"> |
|
|
<i class="fas fa-book-open text-blue-600 ml-2"></i> مصادر إضافية |
|
|
</h2> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
|
|
|
<div class="group border-2 border-dashed border-blue-300 rounded-xl p-8 text-center hover:border-blue-500 transition-colors duration-300 bg-blue-50 hover:bg-blue-100"> |
|
|
<label class="cursor-pointer block"> |
|
|
<input type="file" id="sourceExtraFile" accept=".docx,.pdf,.xlsx,.xls" class="hidden"> |
|
|
<i class="fas fa-upload text-5xl text-blue-500 mb-4"></i> |
|
|
<span class="text-lg text-blue-600 group-hover:text-blue-700">تحميل ملف المصدر</span> |
|
|
</label> |
|
|
</div> |
|
|
|
|
|
<div class="group"> |
|
|
<label class="block text-lg font-bold text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-edit text-blue-600 ml-2"></i> إدخال المصادر يدويًا |
|
|
</label> |
|
|
<textarea id="sourceExtraText" dir="rtl" class="w-full px-6 py-4 border-2 border-blue-200 rounded-xl focus:ring-blue-200 focus:border-blue-400 transition-all resize-none text-lg" rows="6" placeholder="اكتب المصادر هنا..."></textarea> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<button id="submitBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-5 px-8 rounded-xl transition-all transform hover:scale-105 focus:ring-blue-200 text-xl shadow-lg hover:shadow-xl mb-8 pulse-animation"> |
|
|
<div class="flex items-center justify-center"> |
|
|
<i class="fas fa-sync-alt ml-2"></i> تحليل النصوص |
|
|
</div> |
|
|
</button> |
|
|
|
|
|
|
|
|
<div id="resultSection" class="bg-white rounded-xl shadow-md p-6 border border-gray-100 hover:shadow-lg transition-all animate-scale mb-8 hidden card-hover"> |
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-3 flex items-center"> |
|
|
<i class="fas fa-search text-blue-600 ml-2"></i> نتائج التحليل والمقارنة |
|
|
</h2> |
|
|
<div id="errorsList" class="space-y-3 mb-6"></div> |
|
|
|
|
|
|
|
|
<div class="bg-gray-50 p-4 rounded-xl mb-4"> |
|
|
<h3 class="text-lg font-bold mb-3 text-gray-700 flex items-center"> |
|
|
<i class="fas fa-filter text-blue-500 ml-2"></i> فلترة الأخطاء حسب النوع |
|
|
</h3> |
|
|
<div class="error-filters"> |
|
|
<div class="error-filter filter-all active" data-filter="all"> |
|
|
<i class="fas fa-layer-group"></i> جميع الأخطاء |
|
|
</div> |
|
|
<div class="error-filter filter-numbers" data-filter="number"> |
|
|
<i class="fas fa-hashtag"></i> أخطاء الأرقام |
|
|
</div> |
|
|
<div class="error-filter filter-missing" data-filter="missing"> |
|
|
<i class="fas fa-minus-circle"></i> النصوص المفقودة |
|
|
</div> |
|
|
<div class="error-filter filter-meaning" data-filter="meaning"> |
|
|
<i class="fas fa-exclamation-circle"></i> اختلافات المعنى |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="view-selector mb-4"> |
|
|
<div class="view-btn" data-view="segmentView"> |
|
|
<i class="fas fa-th-large"></i> العرض المقسم |
|
|
</div> |
|
|
<div class="view-btn" data-view="interactiveView"> |
|
|
<i class="fas fa-exchange-alt"></i> العرض التفاعلي |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="segmentView" class="view-container active"> |
|
|
<div id="segmentedComparisonContainer" class="space-y-4"> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="analysis-summary mt-6"> |
|
|
<div class="analysis-summary-header"> |
|
|
<i class="fas fa-clipboard-check"></i> شرح الأخطاء في المقاطع |
|
|
</div> |
|
|
<div class="analysis-summary-body" id="segmentViewExplanation"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="interactiveView" class="view-container"> |
|
|
<div class="bg-blue-50 rounded-xl p-6 mb-4"> |
|
|
<div class="flex flex-wrap justify-between items-center mb-4"> |
|
|
<h4 class="text-lg font-bold text-blue-800 flex items-center"> |
|
|
<i class="fas fa-exchange-alt ml-2"></i> عرض تفاعلي للاختلافات |
|
|
</h4> |
|
|
<div class="flex space-x-2 mt-2 sm:mt-0"> |
|
|
<button id="prevDiff" class="bg-blue-200 hover:bg-blue-300 text-blue-800 px-3 py-1 rounded-lg disabled:opacity-50 transition-all"> |
|
|
<i class="fas fa-arrow-right ml-1"></i> السابق |
|
|
</button> |
|
|
<span id="diffCounter" class="bg-white px-3 py-1 rounded-lg">0/0</span> |
|
|
<button id="nextDiff" class="bg-blue-200 hover:bg-blue-300 text-blue-800 px-3 py-1 rounded-lg disabled:opacity-50 transition-all"> |
|
|
<i class="fas fa-arrow-left ml-1"></i> التالي |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div id="currentDiffDisplay" class="bg-white rounded-lg p-4 min-h-[100px] border border-blue-200"> |
|
|
<p class="text-gray-500 text-center">اضغط "التالي" للبدء في استعراض الاختلافات</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="diff-source-target hidden" id="diffDetailedView"> |
|
|
<div class="diff-panel diff-source"> |
|
|
<div class="diff-panel-header"> |
|
|
<i class="fas fa-file-alt"></i> النص في المصدر |
|
|
</div> |
|
|
<div id="diffSourceText"></div> |
|
|
</div> |
|
|
<div class="diff-panel diff-target"> |
|
|
<div class="diff-panel-header"> |
|
|
<i class="fas fa-file-alt"></i> النص في الهدف |
|
|
</div> |
|
|
<div id="diffTargetText"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="diff-reference hidden" id="diffReference"> |
|
|
<div class="reference-header"> |
|
|
<i class="fas fa-info-circle"></i> مرجع الاختلاف |
|
|
</div> |
|
|
<div id="diffReferenceText"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
|
<div class="bg-blue-50 rounded-xl p-4 border border-blue-100"> |
|
|
<h5 class="font-bold text-blue-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-chart-pie ml-2"></i> ملخص الاختلافات |
|
|
</h5> |
|
|
<div id="diffSummary" class="space-y-2"> |
|
|
<div class="flex items-center justify-between bg-white p-2 rounded"> |
|
|
<span>اختلافات الأرقام:</span> |
|
|
<span id="numberDiffCount" class="bg-yellow-100 px-2 py-1 rounded font-bold">0</span> |
|
|
</div> |
|
|
<div class="flex items-center justify-between bg-white p-2 rounded"> |
|
|
<span>النصوص المفقودة:</span> |
|
|
<span id="missingTextCount" class="bg-blue-100 px-2 py-1 rounded font-bold">0</span> |
|
|
</div> |
|
|
<div class="flex items-center justify-between bg-white p-2 rounded"> |
|
|
<span>اختلافات المعنى:</span> |
|
|
<span id="meaningDiffCount" class="bg-red-100 px-2 py-1 rounded font-bold">0</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-green-50 rounded-xl p-4 border border-green-100"> |
|
|
<h5 class="font-bold text-green-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-lightbulb ml-2"></i> توصيات المعالجة |
|
|
</h5> |
|
|
<div id="diffRecommendations" class="space-y-2 text-green-800"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="analysis-summary mt-6"> |
|
|
<div class="analysis-summary-header"> |
|
|
<i class="fas fa-clipboard-check"></i> التفاصيل والتوضيحات |
|
|
</div> |
|
|
<div class="analysis-summary-body" id="interactiveViewExplanation"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="asyncLoadingIndicator" class="async-loading-indicator"> |
|
|
<div class="async-spinner"></div> |
|
|
<div id="asyncLoadingText" class="font-bold text-gray-700 mt-2">جاري معالجة البيانات...</div> |
|
|
<div id="asyncLoadingProgress" class="text-sm text-gray-500 mt-1">0%</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="popup-overlay" class="popup-overlay"></div> |
|
|
<div id="enhancedErrorPopup" class="enhanced-popup"> |
|
|
<div class="enhanced-popup-header"> |
|
|
<div class="enhanced-popup-title"> |
|
|
<i class="fas fa-exclamation-circle"></i> |
|
|
<span id="enhancedPopupTitle">تفاصيل الخطأ</span> |
|
|
</div> |
|
|
<button class="enhanced-popup-close">×</button> |
|
|
</div> |
|
|
<div id="enhancedPopupContent" class="enhanced-popup-body"> |
|
|
|
|
|
</div> |
|
|
<div class="enhanced-popup-footer"> |
|
|
<button id="closeEnhancedPopup" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition-all"> |
|
|
فهمت |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="fullTextDraftSection" class="bg-white rounded-xl shadow-md p-6 border border-gray-100 transition-all animate-scale mb-8 hidden card-hover"> |
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-3 flex items-center"> |
|
|
<i class="fas fa-file-alt text-blue-600 ml-2"></i> مسودة التحليل النصي |
|
|
<span class="draft-marker mr-2">مسودة</span> |
|
|
</h2> |
|
|
<div id="paragraphDivisionsContainer" class="mt-4 space-y-2"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-8 space-y-4 md:space-y-0"> |
|
|
<button id="toggleDraftBtn" class="w-full md:w-auto bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
<i class="fas fa-eye ml-2"></i> عرض/إخفاء مسودة التحليل |
|
|
</button> |
|
|
|
|
|
<div class="flex space-x-4"> |
|
|
<button id="downloadExcelBtn" class="w-full md:w-auto bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
<i class="fas fa-file-excel ml-2"></i> تنزيل التقرير (Excel) |
|
|
</button> |
|
|
<button id="downloadWordBtn" class="w-full md:w-auto bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all"> |
|
|
<i class="fas fa-file-word ml-2"></i> تنزيل التقرير (Word) |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<footer class="copyright"> |
|
|
<p>جميع الحقوق محفوظة لشركة فاست برو للبرمجيات والذكاء الاصطناعي © <span id="currentYear"></span></p> |
|
|
<script>document.getElementById('currentYear').textContent = new Date().getFullYear();</script> |
|
|
</footer> |
|
|
</main> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js'; |
|
|
|
|
|
|
|
|
const RAPIDAPI_KEY = '32769fb369mshfdf6f5e28e26674p1f3764jsn2a31085a1fc7'; |
|
|
const OCR_API_URL = 'https://ocr43.p.rapidapi.com/v1/results'; |
|
|
const DEEPSEEK_API_KEY = 'sk-15606736ed9e4aea8b7cc11a195d2b01'; |
|
|
const DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions'; |
|
|
|
|
|
|
|
|
let fullAnalysisText = ''; |
|
|
let analysisSegments = []; |
|
|
let allDifferences = []; |
|
|
let currentDiffIndex = -1; |
|
|
|
|
|
|
|
|
let ocrPagesCount = 0; |
|
|
let documentPages = []; |
|
|
let selectedPages = []; |
|
|
let extractedTexts = []; |
|
|
let extractedPageNumbers = []; |
|
|
let currentProcessingMode = ''; |
|
|
|
|
|
|
|
|
let canvas = null; |
|
|
let fabricCanvas = null; |
|
|
let originalImageData = null; |
|
|
let isInCropMode = false; |
|
|
let cropRect = null; |
|
|
let currentPageIndex = 0; |
|
|
|
|
|
|
|
|
let sentenceErrors = []; |
|
|
let errorExplanations = {}; |
|
|
|
|
|
|
|
|
let currentErrorFilter = 'all'; |
|
|
|
|
|
|
|
|
let excelWorkbook = null; |
|
|
let currentSheetName = ''; |
|
|
let excelData = null; |
|
|
|
|
|
|
|
|
let selectedFileType = 'normal'; |
|
|
let detectedCountry = null; |
|
|
let correctionApplied = false; |
|
|
let originalTextBeforeCorrection = ''; |
|
|
let correctedText = ''; |
|
|
let correctionChanges = []; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const savedCount = localStorage.getItem('ocrPagesCount'); |
|
|
const lastDate = localStorage.getItem('lastOcrDate'); |
|
|
|
|
|
if (savedCount) { |
|
|
ocrPagesCount = parseInt(savedCount); |
|
|
document.getElementById('ocrCounter').textContent = ocrPagesCount; |
|
|
} |
|
|
|
|
|
if (lastDate) { |
|
|
document.getElementById('lastOcrDate').textContent = lastDate; |
|
|
} |
|
|
|
|
|
|
|
|
initEnhancedPopup(); |
|
|
|
|
|
|
|
|
initFileTypeSelector(); |
|
|
|
|
|
|
|
|
setupViewModes(); |
|
|
|
|
|
|
|
|
document.getElementById('selectAllBtn')?.addEventListener('click', selectAllPages); |
|
|
document.getElementById('deselectAllBtn')?.addEventListener('click', deselectAllPages); |
|
|
document.getElementById('extractTextBtn')?.addEventListener('click', extractText); |
|
|
|
|
|
|
|
|
document.getElementById('copyTextBtn')?.addEventListener('click', copyText); |
|
|
document.getElementById('downloadTextBtn')?.addEventListener('click', downloadText); |
|
|
document.getElementById('useAsSourceBtn')?.addEventListener('click', function() { |
|
|
useOcrText('source'); |
|
|
}); |
|
|
document.getElementById('useAsTargetBtn')?.addEventListener('click', function() { |
|
|
useOcrText('target'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('useExcelAsSourceBtn')?.addEventListener('click', function() { |
|
|
useExcelContent('source'); |
|
|
}); |
|
|
document.getElementById('useExcelAsTargetBtn')?.addEventListener('click', function() { |
|
|
useExcelContent('target'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('rotateLeft')?.addEventListener('click', rotateImageLeft); |
|
|
document.getElementById('rotateRight')?.addEventListener('click', rotateImageRight); |
|
|
document.getElementById('flipHorizontal')?.addEventListener('click', flipImageHorizontal); |
|
|
document.getElementById('flipVertical')?.addEventListener('click', flipImageVertical); |
|
|
document.getElementById('cropImage')?.addEventListener('click', function() { |
|
|
if (isInCropMode) { |
|
|
applyCrop(); |
|
|
} else { |
|
|
activateCropMode(); |
|
|
} |
|
|
}); |
|
|
document.getElementById('resetImage')?.addEventListener('click', resetImage); |
|
|
document.getElementById('improveContrast')?.addEventListener('click', improveContrast); |
|
|
|
|
|
|
|
|
document.getElementById('proceedWithCorrectedText')?.addEventListener('click', function() { |
|
|
|
|
|
document.getElementById('correctionPreview').classList.remove('active'); |
|
|
|
|
|
|
|
|
if (currentProcessingMode === 'source') { |
|
|
document.getElementById('sourceText').value = correctedText; |
|
|
addError('تم تطبيق التصحيحات على النص المصدر', 'info'); |
|
|
} else if (currentProcessingMode === 'target') { |
|
|
document.getElementById('targetText').value = correctedText; |
|
|
addError('تم تطبيق التصحيحات على النص الهدف', 'info'); |
|
|
} else if (currentProcessingMode === 'extra') { |
|
|
document.getElementById('sourceExtraText').value = correctedText; |
|
|
addError('تم تطبيق التصحيحات على المصدر الإضافي', 'info'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('sourceFile').addEventListener('change', function(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (file) { |
|
|
processFile(file, 'source'); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('targetFile').addEventListener('change', function(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (file) { |
|
|
processFile(file, 'target'); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('sourceExtraFile').addEventListener('change', function(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (file) { |
|
|
processFile(file, 'extra'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.view-btn').forEach(btn => { |
|
|
btn.addEventListener('click', function() { |
|
|
|
|
|
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); |
|
|
document.querySelectorAll('.view-container').forEach(c => c.classList.remove('active')); |
|
|
|
|
|
|
|
|
this.classList.add('active'); |
|
|
const viewId = this.getAttribute('data-view'); |
|
|
document.getElementById(viewId).classList.add('active'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.error-filter').forEach(filter => { |
|
|
filter.addEventListener('click', function() { |
|
|
|
|
|
document.querySelectorAll('.error-filter').forEach(f => f.classList.remove('active')); |
|
|
|
|
|
|
|
|
this.classList.add('active'); |
|
|
|
|
|
|
|
|
currentErrorFilter = this.getAttribute('data-filter'); |
|
|
|
|
|
|
|
|
applyErrorFilter(currentErrorFilter); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('toggleDraftBtn').addEventListener('click', function() { |
|
|
const draftSection = document.getElementById('fullTextDraftSection'); |
|
|
draftSection.classList.toggle('hidden'); |
|
|
|
|
|
|
|
|
this.innerHTML = draftSection.classList.contains('hidden') ? |
|
|
'<i class="fas fa-eye ml-2"></i> عرض مسودة التحليل' : |
|
|
'<i class="fas fa-eye-slash ml-2"></i> إخفاء مسودة التحليل'; |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('downloadExcelBtn').addEventListener('click', function() { |
|
|
|
|
|
if (!analysisSegments || analysisSegments.length === 0) { |
|
|
alert('لا توجد نتائج تحليل للتنزيل'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const reportData = []; |
|
|
|
|
|
|
|
|
reportData.push(['رقم المقطع', 'النص المصدر', 'النص الهدف', 'الأخطاء', 'التحليل']); |
|
|
|
|
|
|
|
|
analysisSegments.forEach((segment, index) => { |
|
|
const errors = []; |
|
|
if (segment.errors.numbers > 0) errors.push(`اختلافات أرقام: ${segment.errors.numbers}`); |
|
|
if (segment.errors.missing > 0) errors.push(`نصوص مفقودة: ${segment.errors.missing}`); |
|
|
if (segment.errors.meaning > 0) errors.push(`اختلافات معنى: ${segment.errors.meaning}`); |
|
|
|
|
|
reportData.push([ |
|
|
index + 1, |
|
|
segment.source, |
|
|
segment.target, |
|
|
errors.join('\n'), |
|
|
segment.analysis |
|
|
]); |
|
|
}); |
|
|
|
|
|
|
|
|
const ws = XLSX.utils.aoa_to_sheet(reportData); |
|
|
|
|
|
|
|
|
const wb = XLSX.utils.book_new(); |
|
|
XLSX.utils.book_append_sheet(wb, ws, "تقرير التحليل"); |
|
|
|
|
|
|
|
|
XLSX.writeFile(wb, "تقرير_تحليل_النصوص.xlsx"); |
|
|
|
|
|
} catch (error) { |
|
|
console.error("خطأ في إنشاء ملف Excel:", error); |
|
|
alert("حدث خطأ أثناء إنشاء التقرير"); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('downloadWordBtn').addEventListener('click', function() { |
|
|
|
|
|
downloadWordReport(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function setupViewModes() { |
|
|
|
|
|
const classicViewBtn = document.querySelector('.view-btn[data-view="classicView"]'); |
|
|
const classicViewContainer = document.getElementById('classicView'); |
|
|
|
|
|
|
|
|
if (classicViewBtn) { |
|
|
classicViewBtn.remove(); |
|
|
} |
|
|
|
|
|
|
|
|
if (classicViewContainer) { |
|
|
classicViewContainer.remove(); |
|
|
} |
|
|
|
|
|
|
|
|
const segmentViewBtn = document.querySelector('.view-btn[data-view="segmentView"]'); |
|
|
if (segmentViewBtn) { |
|
|
segmentViewBtn.classList.add('active'); |
|
|
} |
|
|
|
|
|
const segmentViewContainer = document.getElementById('segmentView'); |
|
|
if (segmentViewContainer) { |
|
|
segmentViewContainer.classList.add('active'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function initFileTypeSelector() { |
|
|
const fileTypeOptions = document.querySelectorAll('.file-type-option'); |
|
|
const descriptionElement = document.getElementById('fileTypeDescription'); |
|
|
|
|
|
|
|
|
fileTypeOptions.forEach(option => { |
|
|
option.addEventListener('click', function() { |
|
|
|
|
|
fileTypeOptions.forEach(opt => opt.classList.remove('selected')); |
|
|
|
|
|
|
|
|
this.classList.add('selected'); |
|
|
|
|
|
|
|
|
const radioInput = this.querySelector('input[type="radio"]'); |
|
|
radioInput.checked = true; |
|
|
selectedFileType = radioInput.value; |
|
|
|
|
|
|
|
|
updateFileTypeDescription(); |
|
|
}); |
|
|
|
|
|
|
|
|
const radioInput = option.querySelector('input[type="radio"]'); |
|
|
radioInput.addEventListener('change', function() { |
|
|
if (this.checked) { |
|
|
fileTypeOptions.forEach(opt => opt.classList.remove('selected')); |
|
|
option.classList.add('selected'); |
|
|
selectedFileType = this.value; |
|
|
updateFileTypeDescription(); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
updateFileTypeDescription(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateFileTypeDescription() { |
|
|
const descriptionElement = document.getElementById('fileTypeDescription'); |
|
|
|
|
|
if (selectedFileType === 'normal') { |
|
|
descriptionElement.innerHTML = ` |
|
|
<i class="fas fa-info-circle ml-1"></i> |
|
|
الملف العادي سيتم تحليله مباشرة بدون تصحيح مسبق |
|
|
`; |
|
|
} else if (selectedFileType === 'official') { |
|
|
descriptionElement.innerHTML = ` |
|
|
<i class="fas fa-magic ml-1"></i> |
|
|
المستند الرسمي سيتم تصحيح الثوابت فيه قبل التحليل (مثل تصحيح "الأثم" إلى "الاسم") |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function initEnhancedPopup() { |
|
|
const popup = document.getElementById('enhancedErrorPopup'); |
|
|
const overlay = document.getElementById('popup-overlay'); |
|
|
const closeBtn = document.querySelector('.enhanced-popup-close'); |
|
|
const confirmBtn = document.getElementById('closeEnhancedPopup'); |
|
|
|
|
|
|
|
|
closeBtn.addEventListener('click', closeEnhancedPopup); |
|
|
confirmBtn.addEventListener('click', closeEnhancedPopup); |
|
|
|
|
|
|
|
|
overlay.addEventListener('click', closeEnhancedPopup); |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', function(event) { |
|
|
if (event.key === 'Escape') { |
|
|
closeEnhancedPopup(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function closeEnhancedPopup() { |
|
|
const popup = document.getElementById('enhancedErrorPopup'); |
|
|
const overlay = document.getElementById('popup-overlay'); |
|
|
|
|
|
popup.classList.remove('show'); |
|
|
overlay.classList.remove('show'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function showEnhancedPopup(errorType, errorText, explanation, examples = null) { |
|
|
const popup = document.getElementById('enhancedErrorPopup'); |
|
|
const overlay = document.getElementById('popup-overlay'); |
|
|
const title = document.getElementById('enhancedPopupTitle'); |
|
|
const content = document.getElementById('enhancedPopupContent'); |
|
|
|
|
|
|
|
|
let typeIcon = ''; |
|
|
let typeClass = ''; |
|
|
let typeTitle = ''; |
|
|
|
|
|
if (errorType === 'number') { |
|
|
typeTitle = 'خطأ في الأرقام'; |
|
|
typeIcon = '<i class="fas fa-hashtag text-yellow-500"></i>'; |
|
|
typeClass = 'border-yellow-400'; |
|
|
} else if (errorType === 'missing') { |
|
|
typeTitle = 'نص مفقود'; |
|
|
typeIcon = '<i class="fas fa-minus-circle text-blue-500"></i>'; |
|
|
typeClass = 'border-blue-400'; |
|
|
} else if (errorType === 'meaning') { |
|
|
typeTitle = 'اختلاف في المعنى'; |
|
|
typeIcon = '<i class="fas fa-exclamation-circle text-red-500"></i>'; |
|
|
typeClass = 'border-red-400'; |
|
|
} else { |
|
|
typeTitle = 'تفاصيل الخطأ'; |
|
|
typeIcon = '<i class="fas fa-info-circle text-blue-500"></i>'; |
|
|
typeClass = 'border-blue-400'; |
|
|
} |
|
|
|
|
|
title.innerHTML = `${typeIcon} ${typeTitle}`; |
|
|
|
|
|
|
|
|
let contentHTML = ` |
|
|
<div class="mb-4"> |
|
|
<h3 class="text-xl font-bold mb-3 text-gray-800">النص المحدد:</h3> |
|
|
<div class="p-4 rounded-lg bg-gray-50 border-r-4 ${typeClass} text-lg">${errorText}</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-4"> |
|
|
<h3 class="text-xl font-bold mb-3 text-gray-800">تفسير الخطأ:</h3> |
|
|
<div class="p-4 rounded-lg bg-blue-50 border border-blue-200 text-lg leading-relaxed">${explanation}</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
if (examples) { |
|
|
contentHTML += ` |
|
|
<div class="mb-4"> |
|
|
<h3 class="text-xl font-bold mb-3 text-gray-800">أمثلة توضيحية:</h3> |
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<div class="font-bold flex items-center text-red-700 mb-2"> |
|
|
<i class="fas fa-times-circle ml-2"></i> مثال خاطئ: |
|
|
</div> |
|
|
<div class="error-example">${examples.incorrect}</div> |
|
|
</div> |
|
|
<div> |
|
|
<div class="font-bold flex items-center text-green-700 mb-2"> |
|
|
<i class="fas fa-check-circle ml-2"></i> مثال صحيح: |
|
|
</div> |
|
|
<div class="correct-example">${examples.correct}</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
let illustrationUrl = ''; |
|
|
if (errorType === 'number') { |
|
|
illustrationUrl = 'https://i.ibb.co/Rvfwhdk/number-error.png'; |
|
|
} else if (errorType === 'missing') { |
|
|
illustrationUrl = 'https://i.ibb.co/wYRgxWV/missing-text.png'; |
|
|
} else if (errorType === 'meaning') { |
|
|
illustrationUrl = 'https://i.ibb.co/MPDc1X4/meaning-error.png'; |
|
|
} |
|
|
|
|
|
if (illustrationUrl) { |
|
|
contentHTML += ` |
|
|
<div class="mb-4"> |
|
|
<h3 class="text-xl font-bold mb-3 text-gray-800">توضيح مرئي:</h3> |
|
|
<img src="${illustrationUrl}" alt="توضيح للخطأ" class="error-illustration"> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
let tips = ''; |
|
|
if (errorType === 'number') { |
|
|
tips = ` |
|
|
<li>تأكد من تطابق الأرقام بين النص المصدر والنص الهدف.</li> |
|
|
<li>انتبه للأرقام بمختلف أنظمة كتابتها (عربي، هندي، إنجليزي).</li> |
|
|
<li>تحقق من تنسيق الأرقام مثل استخدام الفواصل العشرية.</li> |
|
|
`; |
|
|
} else if (errorType === 'missing') { |
|
|
tips = ` |
|
|
<li>أضف النص المفقود إلى الترجمة.</li> |
|
|
<li>تأكد من ترجمة جميع عناصر النص المصدر بشكل كامل.</li> |
|
|
<li>ابحث عن أي فقرات أو جمل منسية في النص الهدف.</li> |
|
|
`; |
|
|
} else if (errorType === 'meaning') { |
|
|
tips = ` |
|
|
<li>راجع معنى النص في المصدر والهدف للتأكد من التطابق.</li> |
|
|
<li>استخدم مصطلحات دقيقة ومناسبة للسياق.</li> |
|
|
<li>تجنب الترجمة الحرفية التي قد تؤدي إلى فقدان المعنى الأصلي.</li> |
|
|
`; |
|
|
} |
|
|
|
|
|
if (tips) { |
|
|
contentHTML += ` |
|
|
<div class="p-4 rounded-lg bg-green-50 border border-green-200"> |
|
|
<h3 class="text-xl font-bold mb-3 text-gray-800 flex items-center"> |
|
|
<i class="fas fa-lightbulb text-yellow-500 ml-2"></i> نصائح للتصحيح: |
|
|
</h3> |
|
|
<ul class="list-disc pr-6 space-y-2 text-gray-700"> |
|
|
${tips} |
|
|
</ul> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
content.innerHTML = contentHTML; |
|
|
|
|
|
|
|
|
overlay.classList.add('show'); |
|
|
popup.classList.add('show'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function showLoadingIndicator(text = "جاري معالجة البيانات...", progress = "0%") { |
|
|
const indicator = document.getElementById('asyncLoadingIndicator'); |
|
|
const textElement = document.getElementById('asyncLoadingText'); |
|
|
const progressElement = document.getElementById('asyncLoadingProgress'); |
|
|
|
|
|
textElement.textContent = text; |
|
|
progressElement.textContent = progress; |
|
|
indicator.classList.add('active'); |
|
|
} |
|
|
|
|
|
function hideLoadingIndicator() { |
|
|
const indicator = document.getElementById('asyncLoadingIndicator'); |
|
|
indicator.classList.remove('active'); |
|
|
} |
|
|
|
|
|
function updateLoadingProgress(text, progress) { |
|
|
const textElement = document.getElementById('asyncLoadingText'); |
|
|
const progressElement = document.getElementById('asyncLoadingProgress'); |
|
|
|
|
|
textElement.textContent = text; |
|
|
progressElement.textContent = progress; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function correctOfficialDocument(text, targetType) { |
|
|
try { |
|
|
|
|
|
document.getElementById('correctionStatus').classList.add('active'); |
|
|
document.getElementById('correctionMessage').textContent = 'جاري تحليل النص وتحديد الدولة...'; |
|
|
document.getElementById('correctionProgressBar').style.width = '10%'; |
|
|
|
|
|
|
|
|
showLoadingIndicator('جاري تحليل المستند الرسمي وتصحيح الثوابت...', '10%'); |
|
|
|
|
|
|
|
|
originalTextBeforeCorrection = text; |
|
|
|
|
|
|
|
|
detectedCountry = await detectCountryFromText(text); |
|
|
|
|
|
document.getElementById('correctionMessage').textContent = `تم تحديد الدولة: ${detectedCountry}. جاري تطبيق قواعد التصحيح...`; |
|
|
document.getElementById('correctionProgressBar').style.width = '40%'; |
|
|
updateLoadingProgress(`تم تحديد الدولة: ${detectedCountry}. جاري تطبيق قواعد التصحيح...`, '40%'); |
|
|
|
|
|
|
|
|
const correctionResult = await applyCorrectionRules(text, detectedCountry); |
|
|
|
|
|
document.getElementById('correctionProgressBar').style.width = '80%'; |
|
|
updateLoadingProgress('جاري تطبيق التصحيحات النهائية...', '80%'); |
|
|
|
|
|
|
|
|
correctedText = correctionResult.correctedText; |
|
|
correctionChanges = correctionResult.changes; |
|
|
|
|
|
document.getElementById('correctionMessage').textContent = `تم تطبيق ${correctionChanges.length} تصحيح بنجاح`; |
|
|
document.getElementById('correctionProgressBar').style.width = '100%'; |
|
|
updateLoadingProgress(`تم تطبيق ${correctionChanges.length} تصحيح بنجاح`, '100%'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
document.getElementById('correctionStatus').classList.remove('active'); |
|
|
hideLoadingIndicator(); |
|
|
showCorrectionPreview(); |
|
|
}, 1000); |
|
|
|
|
|
correctionApplied = true; |
|
|
return correctedText; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في تصحيح المستند الرسمي:', error); |
|
|
document.getElementById('correctionStatus').classList.remove('active'); |
|
|
hideLoadingIndicator(); |
|
|
addError('حدث خطأ أثناء تصحيح المستند الرسمي: ' + error.message, 'error'); |
|
|
return text; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function detectCountryFromText(text) { |
|
|
try { |
|
|
const prompt = `حلل النص التالي وحدد الدولة ونوع المستند التي ينتمي إليها هذا المستند الرسمي بناءً على: |
|
|
1. المصطلحات المستخدمة |
|
|
2. أسلوب الكتابة الرسمية |
|
|
3. الكلمات المميزة للدولة |
|
|
4.تحديد نوع المستند اذا كان (سجل تجاري - برينت تأميني - شهادة تحركات -شهادة ميلاد - قيد عائلي - بطاقة ضريبية - برينت - رخصة سيارة) |
|
|
|
|
|
|
|
|
أجب بكلمة كلمتين فقط اسم الدولة بالعربيةو نوع الملف . |
|
|
|
|
|
النص: |
|
|
${text.substring(0, 500)}`; |
|
|
|
|
|
const payload = { |
|
|
model: "deepseek-chat", |
|
|
messages: [ |
|
|
{ role: "system", content: "أنت خبير في تحديد مصدر المستندات الرسمية العربية" }, |
|
|
{ role: "user", content: prompt } |
|
|
], |
|
|
temperature: 0.1, |
|
|
max_tokens: 50 |
|
|
}; |
|
|
|
|
|
const response = await fetch(DEEPSEEK_API_URL, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY, |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error('فشل في تحديد الدولة'); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
const country = data.choices[0].message.content.trim(); |
|
|
|
|
|
|
|
|
const cleanCountry = country.replace(/[^\u0600-\u06FF\s]/g, '').trim(); |
|
|
|
|
|
return cleanCountry || 'مصر'; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في تحديد الدولة:', error); |
|
|
return 'مصر'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function applyCorrectionRules(text, country) { |
|
|
try { |
|
|
const prompt = `صحح الأخطاء في الثوابت فقط (وليس المتغيرات) في النص التالي من مستند رسمي ${country}ي: |
|
|
|
|
|
قواعد التصحيح: |
|
|
|
|
|
1. حدد نوع المستند الرسمي أولًا (شهادة ميلاد، بطاقة ضريبية، قيد عائلي، رخصة سيارة)، ثم استخدم القوالب التالية حسب نوع المستند: |
|
|
|
|
|
قوالب المستندات: |
|
|
|
|
|
- شهادة ميلاد: |
|
|
"""جمهورية مصر العربية |
|
|
|
|
|
وزارة الداخلية – مصلحة الأحوال المدنية |
|
|
|
|
|
نسخة من شهادة الميلاد |
|
|
|
|
|
الرقم القومي للمولود: 31011030102371 |
|
|
|
|
|
بيانات المولود: |
|
|
|
|
|
الاسم: طلال |
|
|
|
|
|
الجنسية: مصري |
|
|
|
|
|
الديانة: مسلم |
|
|
|
|
|
مكان الميلاد: القاهرة / قسم شرطة أول مدينة نصر |
|
|
|
|
|
النوع: ذكر |
|
|
|
|
|
تاريخ الميلاد: 03 نوفمبر 2010 |
|
|
|
|
|
بيانات الأب: |
|
|
|
|
|
الاسم: عصام سعد محمد محمد غانم |
|
|
|
|
|
الجنسية: مصري |
|
|
|
|
|
الديانة: مسلم |
|
|
|
|
|
الرقم القومي: 27608020102772 |
|
|
|
|
|
بيانات الأم: |
|
|
|
|
|
الاسم: نورة رضا إسماعيل حسين |
|
|
|
|
|
الجنسية: مصري |
|
|
|
|
|
الديانة: مسلم |
|
|
|
|
|
الرقم القومي: 28707132100081 |
|
|
|
|
|
مركز الصحة: قسم شرطة ثالث مدينة نصر |
|
|
|
|
|
رقم القيد: 1737 |
|
|
|
|
|
قسم القيد المدني: قسم شرطة أول مدينة نصر |
|
|
|
|
|
تاريخ القيد: 07/11/2010 |
|
|
|
|
|
الجهة المُصدرة: سجل مدني قسم أول مدينة نصر |
|
|
|
|
|
تاريخ الإصدار: 10/03/2015 |
|
|
|
|
|
الرقم التسلسلي: 1687547752 |
|
|
|
|
|
تأكد من وجود ختم الطفولة والأمومة فئة جنيه واحد، العلامة المائية، وشعار مصر – شهادة الأحوال المدنية. |
|
|
|
|
|
ختم: الشرطة – أرجاء وزارة الداخلية""" |
|
|
|
|
|
- بطاقة ضريبية:"""شعار: وزارة المالية |
|
|
|
|
|
الجمهورية: جمهورية مصر العربية |
|
|
|
|
|
الهيئة: الهيئة المصرية للضرائب |
|
|
|
|
|
الإدارة الأولى شرطة مدينة نصر |
|
|
|
|
|
ملكية فردية |
|
|
|
|
|
اسم المشروع: Waffle Break Food & Drinks |
|
|
|
|
|
اسم المكلف: عماد جاد الله حنا جاد الله |
|
|
|
|
|
العنوان: 206 بالاستثمار الكولج في قرى النادي الأهلي – مدينة نصر |
|
|
|
|
|
نشاط المشروع: تجارة التجزئة للمواد الغذائية |
|
|
|
|
|
تاريخ بدء النشاط: 20/01/2015 |
|
|
|
|
|
كود النشاط: 4721 |
|
|
|
|
|
[رمز الاستجابة السريعة QR Code] |
|
|
|
|
|
رقم البطاقة: 537-544-348 |
|
|
|
|
|
تاريخ الإصدار: 08/12/2022 |
|
|
|
|
|
تاريخ الانتهاء: 07/12/2027 |
|
|
|
|
|
كود الأمان الضريبي: 4621216266872540 |
|
|
|
|
|
توجيهات: |
|
|
|
|
|
تُستخدم هذه البطاقة من قبل المكلف أو وكيله الشرعي. |
|
|
|
|
|
في حال العثور عليها، يرجى تسليمها لأقرب مكتب ضرائب. |
|
|
|
|
|
في حالة الفقد أو السرقة، الاتصال على 16395 أو (02) 27929911. |
|
|
""" |
|
|
- قيد عائلي:"""وزارة الداخلية |
|
|
|
|
|
مصلحة الأحوال المدنية |
|
|
|
|
|
شهادة القيد العائلي الإلكترونية |
|
|
|
|
|
[باركود] |
|
|
|
|
|
الرقم القومي: 2 810615 14 01897 |
|
|
|
|
|
الجهة الطالبة: سفارة فرنسا |
|
|
|
|
|
البيانات الشخصية |
|
|
الاسم: محمود شرف الدين أحمد شرف الدين شباكة |
|
|
|
|
|
الاسم المختصر: |
|
|
|
|
|
الحالة الاجتماعية: متزوج |
|
|
|
|
|
الديانة: مسلم |
|
|
|
|
|
النوع: ذكر |
|
|
|
|
|
الجنسية: مصري |
|
|
|
|
|
اسم الأم: بسيمة إسماعيل بنداري |
|
|
|
|
|
تاريخ الميلاد: 15/06/1981 |
|
|
|
|
|
كتابة بالأحرف: الخامس عشر من يونيو عام ألف وتسعمائة وواحد وثمانين |
|
|
|
|
|
محل الميلاد: القليوبية / مركز بنها |
|
|
|
|
|
بيانات القيد: |
|
|
مركز بنها / الرملة |
|
|
|
|
|
رقم القيد: 249 |
|
|
|
|
|
بيانات الزوجات |
|
|
م اسم الزوجة الرقم القومي اسم الأم رقم وثيقة الزواج تاريخ الحالة الحالة ملاحظات |
|
|
1 نهى فكري عبد الوهاب محمود 2 811003 14 00983 سميرة 518 08/08/2006 زواج |
|
|
137 25/07/2008 طلاق |
|
|
2 هبة عبد سيد أحمد عبد الجواد 2 861001 14 14662 وفاء 219 04/02/2009 زواج |
|
|
|
|
|
بيانات الأولاد |
|
|
عدد الأبناء: 3 |
|
|
|
|
|
م الاسم الأول الرقم القومي رقم القيد تاريخ القيد اسم الأم الديانة/النوع ملاحظات |
|
|
1 سعيد 3 130731 14 01498 3512 03/08/2013 هبة عبد سيد أحمد مسلم/ذكر |
|
|
2 مي 3 200423 14 01862 2769 28/04/2020 هبة عبد سيد أحمد مسلمة/أنثى |
|
|
3 سيف 3 100121 14 00654 387 23/01/2010 هبة عبد سيد أحمد مسلم/ذكر |
|
|
|
|
|
البيانات الواردة أعلاه مطابقة للبيانات المسجلة في النظام، وتم تقديم المستندات من قِبل صاحب الشأن وتحت مسؤوليته الشخصية. |
|
|
|
|
|
الاسم الرسمي: داليا رجب سعد |
|
|
|
|
|
تمت المراجعة بواسطة: //توقيع بخط اليد// |
|
|
|
|
|
جهة الإصدار: إدارة القليوبية |
|
|
|
|
|
التوقيع: //توقيع بخط اليد// |
|
|
|
|
|
تاريخ الإصدار: 07/04/2025 |
|
|
|
|
|
اعتماد |
|
|
|
|
|
[ختم] |
|
|
|
|
|
وزارة الداخلية |
|
|
|
|
|
مصلحة الأحوال المدنية |
|
|
|
|
|
صفحة 1/1 |
|
|
|
|
|
وزارة الداخلية |
|
|
|
|
|
مصلحة الأحوال المدنية |
|
|
|
|
|
طلب شهادة القيد العائلي |
|
|
|
|
|
خدمة خاصة |
|
|
|
|
|
نموذج (6/40) |
|
|
|
|
|
مقدم الطلب: |
|
|
|
|
|
التاريخ:""" |
|
|
|
|
|
- رخصة سيارة: |
|
|
"""جمهورية مصر العربية |
|
|
|
|
|
وزارة الداخلية – إدارة مرور القاهرة |
|
|
|
|
|
صاحب الرخصة: |
|
|
عماد جاد الله حنا |
|
|
|
|
|
العنوان: مبنى 42، مدخل 23، مدينتي، القاهرة |
|
|
|
|
|
تاريخ الإصدار: 01/12/2024 |
|
|
|
|
|
تاريخ الانتهاء: 30/11/2027 |
|
|
|
|
|
وحدة المرور: مدينة نصر الجديدة |
|
|
|
|
|
نوع المركبة: سيارة خاصة (ليموزين) |
|
|
|
|
|
الماركة: مرسيدس C200 موديل 2023 |
|
|
|
|
|
الشاصي: W1KAF4CB4PR124646 |
|
|
|
|
|
المحرك: 184636 |
|
|
|
|
|
عدد الأسطوانات: 4 بنزين |
|
|
|
|
|
اللوان: أسود |
|
|
|
|
|
سعة المحرك: 1498 سم³ |
|
|
|
|
|
الهيئة العامة للتأمين على المركبات: |
|
|
|
|
|
رقم الوثيقة: 900000035636706 |
|
|
|
|
|
رقم الملصق الإلكتروني: H 5788757 |
|
|
|
|
|
ختم وتوقيع اللواء/ محمد عبد الله |
|
|
"""" |
|
|
- سجل تجاري: |
|
|
"""وزارة التموين والتجارة الداخلية |
|
|
|
|
|
جهاز تنمية التجارة الداخلية |
|
|
|
|
|
الإدارة المركزية للسجل التجاري |
|
|
|
|
|
مكتب سجل تجاري غرفة تجارة طنطا |
|
|
|
|
|
مستخرج من السجل التجاري رقم: 39188 (شركة تضامن – المكتب الرئيسي – عدد الفروع 0 من 0) |
|
|
|
|
|
الرقم القومي للمنشأة: 487095936 |
|
|
الرقم الموحد للسجل التجاري |
|
|
(10820 02000 39188) |
|
|
|
|
|
أي حذف أو تعديل أو كشط أو إضافة أو أختام بخلاف ختم شعار جمهورية مصر العربية يبطل هذا المستند |
|
|
|
|
|
صفحة 1 من 2 |
|
|
|
|
|
0043 02-07-2025 09:56:20 |
|
|
|
|
|
[باركود] |
|
|
|
|
|
(1) |
|
|
|
|
|
(أ) رقم الإيداع |
|
|
|
|
|
(ب) تاريخ الإيداع |
|
|
|
|
|
(ج) رقم القيد في السجل التجاري |
|
|
|
|
|
(2) |
|
|
|
|
|
(أ) رقم إيداع طلب التجديد |
|
|
|
|
|
(ب) تاريخ إيداع طلب التجديد |
|
|
|
|
|
(ج) تاريخ التجديد |
|
|
|
|
|
(أ) |
|
|
|
|
|
الاسم التجاري |
|
|
|
|
|
اسم العلامة التجارية |
|
|
|
|
|
أول اسم تجاري |
|
|
|
|
|
(ب) |
|
|
|
|
|
الشكل القانوني للشركة |
|
|
|
|
|
عنوان الشركة أو اسم الشركة أو اسم الجمعية التعاونية |
|
|
|
|
|
اسم العلامة التجارية |
|
|
|
|
|
اسم العلامة التجارية للفرع |
|
|
|
|
|
اسم ولقب التاجر، الجنسية، مكان وتاريخ الميلاد |
|
|
|
|
|
اسم ولقب الشركاء المتضامنين في شركات التضامن أو التوصية بالإضافة إلى جنسيتهم وتاريخ ومكان ميلادهم |
|
|
|
|
|
اسم ولقب الشركاء أو غيرهم المعينين لإدارة الشركة والمخولين بالتوقيع نيابة عنها، وصفاتهم، ومستوى سلطتهم في الإدارة والتوقيع |
|
|
|
|
|
اسم ولقب أعضاء مجلس الإدارة في شركات المساهمة أو التعاونية ووكلاء المجلس وصفاتهم وتاريخ ومكان الميلاد وجنسيتهم ومستوى سلطتهم في الإدارة والتوقيع |
|
|
|
|
|
اسم ولقب رئيس الفرع الرئيسي أو الوكالة العامة في جمهورية مصر العربية بالإضافة إلى تاريخ ومكان الميلاد والجنسية إذا كان الفرع الرئيسي بالخارج |
|
|
|
|
|
(أ) نوع التجارة |
|
|
|
|
|
أو |
|
|
|
|
|
(ب) غرض الشركة أو الجمعية التعاونية |
|
|
|
|
|
(أ) |
|
|
|
|
|
1- تاريخ بدء ممارسة النشاط بجمهورية مصر العربية |
|
|
|
|
|
2- تاريخ الترخيص بمزاولة العمل |
|
|
|
|
|
(ب) تاريخ بدء وانتهاء الشركة وتاريخ رخصة مزاولة العمل أو موافقة هيئة الاستثمار |
|
|
|
|
|
(ج) تاريخ افتتاح الفرع أو الوكالة |
|
|
|
|
|
(أ) عنوان المكتب الرئيسي |
|
|
|
|
|
(ب) عنوان المكتب العمومي أو الجمعية التعاونية |
|
|
|
|
|
(ج) رقم قيد المكتب الرئيسي أو المكتب العمومي (في حالة قيد فرع) |
|
|
|
|
|
(أ) عناوين الفروع والوكالات التابعة للمكتب الرئيسي أو العمومي |
|
|
|
|
|
(ب) عنوان الفرع أو الوكالة (في حالة قيد فرع) |
|
|
|
|
|
(1) |
|
|
A-2798 |
|
|
B-24/04/2019 |
|
|
C-39188 |
|
|
|
|
|
(2) |
|
|
A-4597 |
|
|
B-02/07/2024 |
|
|
ساري حتى |
|
|
23/04/2029 |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
(ب) |
|
|
|
|
|
2- رضا حسن محمد إمام وشريكه/// |
|
|
|
|
|
3- لا يوجد /// |
|
|
|
|
|
قانون رقم 34 لسنة 1976 |
|
|
|
|
|
(أ) |
|
|
|
|
|
شركة تضامن |
|
|
خاصة |
|
|
عقد رقم 957/أ لسنة 2019، التوقيع موثق |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
(ب) |
|
|
|
|
|
رضا حسن محمد إمام، مواليد 01/05/1988 |
|
|
مصر – الغربية – رقم قومي 28805011603096 – شريك مدير وله حق الإدارة والتوقيع منفردًا باسم الشركة /// |
|
|
|
|
|
أمل حسن محمد حسن – مواليد 06/12/1972 |
|
|
مصر – الغربية – رقم قومي 27212061600808 – شريك متضامن/// |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
النشاط الرئيسي: إنتاج الخبز البلدي |
|
|
الكود: 107101 |
|
|
تاريخ بدء النشاط: 24/04/2019 |
|
|
|
|
|
(ب) |
|
|
|
|
|
مخبز بلدي آلي بالكامل /// |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
مدة الشركة 9 سنوات و36 شهرًا تبدأ من 22/04/2019 حتى 21/04/2028 /// |
|
|
|
|
|
[ملاحظات] |
|
|
قابلة للتجديد، وصدر ترخيص التشغيل في 23/04/2019 |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
(أ) |
|
|
|
|
|
الغربية – كفر قريطنة – المحلة الكبرى |
|
|
المركز – مملوك للسيدة/ عزيزة عبد العزيز البستاوي/// |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
(أ) |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
اسم مقدم الطلب: إبراهيم محمد الششتاوي أحمد سرحان – رقم الطلب 4700719 |
|
|
تم دفع الرسوم (رسوم تأمين خصائص الوثيقة) وقدرها 162 جنيه مصري (عدد 1 نسخة) بموجب إيصال بتاريخ 02/07/2025 |
|
|
|
|
|
صادر من مكتب غرفة تجارة طنطا بتاريخ 02/07/2025 – محرر الوثيقة: فاتن عبد المنعم رشاد محمد طلبة |
|
|
|
|
|
[باركود] |
|
|
|
|
|
[ختم] |
|
|
وزارة التموين والتجارة الداخلية – جهاز تنمية التجارة الداخلية |
|
|
|
|
|
[ختم] |
|
|
وزارة التموين والتجارة الداخلية – جهاز تنمية التجارة الداخلية |
|
|
|
|
|
وزارة التموين والتجارة الداخلية |
|
|
|
|
|
جهاز تنمية التجارة الداخلية |
|
|
|
|
|
الإدارة المركزية للسجل التجاري |
|
|
|
|
|
مكتب سجل تجاري غرفة تجارة طنطا |
|
|
|
|
|
مستخرج من السجل التجاري رقم: 39188 (شركة تضامن – المكتب الرئيسي – عدد الفروع 0 من 0) |
|
|
|
|
|
الرقم القومي للمنشأة: 487095936 |
|
|
الرقم الموحد للسجل التجاري |
|
|
(10820 02000 39188) |
|
|
|
|
|
أي حذف أو تعديل أو كشط أو إضافة أو أختام بخلاف ختم شعار جمهورية مصر العربية يبطل هذا المستند |
|
|
|
|
|
صفحة 2 من 2 |
|
|
|
|
|
0043 02-07-2025 09:56:20 |
|
|
|
|
|
[باركود] |
|
|
|
|
|
الوكلاء المفوضون |
|
|
|
|
|
مدير الفرع أو الوكالة؛ الاسم، اللقب، تاريخ ومكان الميلاد، والجنسية لكل منهم |
|
|
|
|
|
رأس المال المستثمر في المكتب الرئيسي للتاجر، والفروع، والوكالات التابعة للتاجر |
|
|
رأس مال الشركة أو الجمعية التعاونية |
|
|
رأس المال المدفوع من قبل التاجر |
|
|
المبلغ الذي التزم الشركاء بسداده |
|
|
حصص الشركاء الموصيين |
|
|
الحصص العينية إن وجدت |
|
|
رصيد مدين الفرع للمكتب العمومي للشركة |
|
|
|
|
|
(أ) مكاتب التاجر السابقة |
|
|
(ب) المكاتب الحالية |
|
|
في سجلات نفس المكتب |
|
|
في سجلات مكاتب أخرى |
|
|
|
|
|
رقم القيد |
|
|
العلامات التجارية |
|
|
براءات الاختراع |
|
|
الرسومات والنماذج الصناعية |
|
|
|
|
|
نظام الزوجية |
|
|
الأهلية التجارية |
|
|
كل عقد بحل الشركة أو وضعها تحت التصفية |
|
|
|
|
|
[ختم] |
|
|
وزارة التموين والتجارة الداخلية – جهاز تنمية التجارة الداخلية |
|
|
|
|
|
[ختم] |
|
|
وزارة التموين والتجارة الداخلية – جهاز تنمية التجارة الداخلية |
|
|
|
|
|
(أ) الأحكام الصادرة بتنفيذ مصادرة أموال التاجر أو تعيين أوصياء ووكلاء ليحلوا محل الغائبين بعزل أو إزالة الرهن |
|
|
|
|
|
(ب) الأحكام والقرارات الخاصة بالإفلاس |
|
|
(ج) أحكام الإعادة |
|
|
(د) الأحكام الصادرة بشأن الطلاق، والانفصال البدني أو المالي، والأحكام الصادرة بشأن حالة تصريح العمل (مسموح، مقيد، أو ملغى) |
|
|
(هـ) الأحكام القضائية الخاصة بفصل الشركاء، أو عزل المدير، أو حل الشركات أو إنهائها، وتعيين/عزل المصفيين |
|
|
(و) بيانات البيع/الرهن الخاص بالمحل التجاري وفقًا للقانون رقم 11 لسنة 1940 |
|
|
|
|
|
هامش |
|
|
|
|
|
(8) |
|
|
(9) |
|
|
(10) |
|
|
(11) |
|
|
(12) |
|
|
(13) |
|
|
(14) |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
(ب) |
|
|
|
|
|
قيمة رأس المال 1000 جنيه مصري فقط |
|
|
ألف جنيه مصري فقط /// |
|
|
|
|
|
[1] 3793 |
|
|
22/05/2019 |
|
|
|
|
|
قيمة رأس المال 18000 جنيه مصري فقط |
|
|
ثمانية عشر ألف جنيه مصري فقط /// |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
نهاية البيانات |
|
|
|
|
|
markdown |
|
|
Copy |
|
|
Edit |
|
|
*نهاية البيانات* |
|
|
[1] 3793 22/05/2019 |
|
|
عقد موثق التوقيع |
|
|
|
|
|
[2] 4597 02/07/2024 |
|
|
|
|
|
اسم مقدم الطلب: إبراهيم محمد الششتاوي أحمد سرحان – رقم الطلب 4700719 |
|
|
تم دفع الرسوم (رسوم تأمين خصائص الوثيقة) وقدرها 162 جنيه مصري (عدد 1 نسخة) بموجب إيصال بتاريخ 02/07/2025 |
|
|
|
|
|
صادر من مكتب غرفة تجارة طنطا بتاريخ 02/07/2025 – محرر الوثيقة: فاتن عبد المنعم رشاد محمد طلبة |
|
|
|
|
|
[باركود] |
|
|
|
|
|
مدير المكتب |
|
|
|
|
|
[توقيع] |
|
|
""" |
|
|
|
|
|
- شهادة تحركات: |
|
|
"""وزارة الداخلية |
|
|
الإدارة العامة للجوازات والهجرة والجنسية |
|
|
|
|
|
إلى السيد/ قنصل عام سفارة اليونان |
|
|
|
|
|
الموضوع: شهادة تحركات |
|
|
|
|
|
بناءً على طلب السيد/ مينا صدقي إبراهيم لاندوس – الجنسية: مصري – مواليد: 22/09/1992 |
|
|
|
|
|
رقم جواز السفر: A27179114 – سنة الإصدار: 2020 – نوعه: عادي – جهة الإصدار: المنتزه |
|
|
عدد التحركات: 2 |
|
|
- مغادرة: 02/05/2025 – مصر / الإمارات العربية المتحدة |
|
|
- عودة: 09/05/2025 – الإمارات العربية المتحدة / مصر |
|
|
|
|
|
حررت في: 28/06/2025 |
|
|
توقيع: اللواء د. محمود محمد كمره |
|
|
رئيس الإدارة العامة للجوازات والهجرة والجنسية |
|
|
""" |
|
|
|
|
|
- بيان تأمين/معاش: |
|
|
"""الهيئة القومية للتأمين الاجتماعي |
|
|
بيانات صرف منتظم للمستفيد |
|
|
|
|
|
اسم المستحق: عبد المسيح صبري زكري – رقم تأمين: 11450825 – رقم قومي: 24911040100133 |
|
|
القطاع: قانون 75/79 للقطاع الخاص |
|
|
مكتب المتابعة: شرق القاهرة – قسم أول مدينة نصر |
|
|
تاريخ بدء الصرف: 01/04/2023 |
|
|
جهة الصرف: البنك الأهلي المصري – فرع العروبة – نوع الحساب: جاري عادي |
|
|
رقم الحساب: EG110003013430104794009000140 |
|
|
العنوان: 7 شارع الأهرام – مصر الجديدة |
|
|
|
|
|
إجمالي الاستحقاق: 2341.71 جنيه |
|
|
الاستقطاعات: 17.30 جنيه |
|
|
صافي المعاش: 2320.00 جنيه |
|
|
""" |
|
|
|
|
|
2. صحح أخطاء الكلمات الثابتة مثل: |
|
|
- "الأثم" → "الاسم" |
|
|
- "تارخ" → "تاريخ" |
|
|
- "الموليد" → "المولد" |
|
|
- "الحنسية" → "الجنسية" |
|
|
- "الرقو" → "الرقم" |
|
|
- "الهويه" → "الهوية" |
|
|
- "البيناات" → "البيانات" |
|
|
- "الشحصية" → "الشخصية" |
|
|
- "الأحوال الشحصية" → "الأحوال الشخصية" |
|
|
- "اسم الشخس" → "اسم الشخص" |
|
|
- "رقم البطقه" → "رقم البطاقة" |
|
|
- "تارخ الأصدار" → "تاريخ الإصدار" |
|
|
- "تارخ الانتهاء" → "تاريخ الانتهاء" |
|
|
- "جهة الأصدار" → "جهة الإصدار" |
|
|
- "مصلحه الأحول" → "مصلحة الأحوال" |
|
|
- "مركر صحة" → "مركز صحة" |
|
|
- "تاريج" → "تاريخ" |
|
|
- "الجهة المصدرة" → "الجهة المُصدرة" |
|
|
- "رقم القومي" → "الرقم القومي" |
|
|
- "الرقم التلسلي" → "الرقم التسلسلي" |
|
|
|
|
|
3. صحح الأخطاء الإملائية في المصطلحات الرسمية حسب معايير ${country}. |
|
|
|
|
|
4. لا تُغيّر أي بيانات شخصية (الأسماء، التواريخ، الأرقام، العناوين). |
|
|
|
|
|
5. احتفظ بالتنسيق الأصلي للنص تماماً. |
|
|
|
|
|
أعِد النص المُصحح مع قائمة بالتغييرات في هذا الشكل: |
|
|
النص المُصحح: |
|
|
[النص هنا] |
|
|
|
|
|
التغييرات المطبقة: |
|
|
- [الكلمة الخطأ] → [الكلمة الصحيحة] |
|
|
|
|
|
النص الأصلي: |
|
|
${text}`; |
|
|
|
|
|
const payload = { |
|
|
model: "deepseek-chat", |
|
|
messages: [ |
|
|
{ role: "system", content: "أنت خبير في تصحيح المستندات الرسمية العربية بناء على الاساسيات او النماذج التجريبية الموضوعة كقالب جاهز" }, |
|
|
{ role: "user", content: prompt } |
|
|
], |
|
|
temperature: 0.2, |
|
|
max_tokens: 4000 |
|
|
}; |
|
|
|
|
|
const response = await fetch(DEEPSEEK_API_URL, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY, |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error('فشل في تطبيق قواعد التصحيح'); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
const result = data.choices[0].message.content.trim(); |
|
|
|
|
|
|
|
|
const correctedTextMatch = result.match(/النص المُصحح:\s*([\s\S]*?)(?=التغييرات المطبقة:|$)/); |
|
|
const changesMatch = result.match(/التغييرات المطبقة:\s*([\s\S]*)/); |
|
|
|
|
|
let correctedText = text; |
|
|
let changes = []; |
|
|
|
|
|
if (correctedTextMatch) { |
|
|
correctedText = correctedTextMatch[1].trim(); |
|
|
} |
|
|
|
|
|
if (changesMatch) { |
|
|
const changesText = changesMatch[1].trim(); |
|
|
|
|
|
const changeLines = changesText.split('\n').filter(line => line.includes('→')); |
|
|
changes = changeLines.map(line => { |
|
|
const parts = line.replace(/^-\s*/, '').split('→'); |
|
|
if (parts.length === 2) { |
|
|
return { |
|
|
original: parts[0].trim(), |
|
|
corrected: parts[1].trim() |
|
|
}; |
|
|
} |
|
|
return null; |
|
|
}).filter(change => change !== null); |
|
|
} |
|
|
|
|
|
return { |
|
|
correctedText: correctedText, |
|
|
changes: changes |
|
|
}; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في تطبيق قواعد التصحيح:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function showCorrectionPreview() { |
|
|
const previewElement = document.getElementById('correctionPreview'); |
|
|
const changesElement = document.getElementById('correctionChanges'); |
|
|
|
|
|
if (correctionChanges.length === 0) { |
|
|
changesElement.innerHTML = '<p class="text-gray-500">لم يتم العثور على أخطاء تحتاج إلى تصحيح في الثوابت</p>'; |
|
|
} else { |
|
|
let changesHTML = ''; |
|
|
correctionChanges.forEach(change => { |
|
|
changesHTML += ` |
|
|
<div class="mb-2"> |
|
|
<span class="original-text">${change.original}</span> |
|
|
<span class="mx-2">→</span> |
|
|
<span class="corrected-text">${change.corrected}</span> |
|
|
</div> |
|
|
`; |
|
|
}); |
|
|
changesElement.innerHTML = changesHTML; |
|
|
} |
|
|
|
|
|
previewElement.classList.add('active'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function processFile(file, targetType) { |
|
|
if (!file) return; |
|
|
|
|
|
document.getElementById('processingStatus').classList.remove('hidden'); |
|
|
document.getElementById('statusText').textContent = 'جاري فحص نوع الملف...'; |
|
|
document.getElementById('progressBar').style.width = '10%'; |
|
|
|
|
|
|
|
|
showLoadingIndicator('جاري فحص وتجهيز الملف...', '10%'); |
|
|
|
|
|
currentProcessingMode = targetType; |
|
|
|
|
|
|
|
|
if (file.type === 'application/pdf') { |
|
|
processPDF(file, targetType); |
|
|
} else if (file.type.startsWith('image/')) { |
|
|
processImage(file, targetType); |
|
|
} else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || |
|
|
file.name.toLowerCase().endsWith('.docx')) { |
|
|
processDocx(file, targetType); |
|
|
} else if (file.type === 'application/vnd.ms-excel' || |
|
|
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || |
|
|
file.name.toLowerCase().endsWith('.xlsx') || |
|
|
file.name.toLowerCase().endsWith('.xls')) { |
|
|
processExcel(file, targetType); |
|
|
} else { |
|
|
|
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
alert('نوع الملف غير مدعوم. يرجى تحميل ملف PDF أو صورة أو Word أو Excel.'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function processPDF(file, targetType) { |
|
|
try { |
|
|
document.getElementById('statusText').textContent = 'جاري معالجة ملف PDF...'; |
|
|
document.getElementById('progressBar').style.width = '20%'; |
|
|
updateLoadingProgress('جاري معالجة ملف PDF...', '20%'); |
|
|
|
|
|
const arrayBuffer = await file.arrayBuffer(); |
|
|
const loadingTask = pdfjsLib.getDocument(arrayBuffer); |
|
|
const pdf = await loadingTask.promise; |
|
|
|
|
|
documentPages = []; |
|
|
selectedPages = []; |
|
|
|
|
|
document.getElementById('progressBar').style.width = '50%'; |
|
|
updateLoadingProgress('جاري استخراج صفحات PDF...', '50%'); |
|
|
|
|
|
for (let i = 1; i <= pdf.numPages; i++) { |
|
|
const page = await pdf.getPage(i); |
|
|
const viewport = page.getViewport({ scale: 1.5 }); |
|
|
const canvas = document.createElement('canvas'); |
|
|
const context = canvas.getContext('2d'); |
|
|
canvas.height = viewport.height; |
|
|
canvas.width = viewport.width; |
|
|
|
|
|
updateLoadingProgress(`جاري استخراج الصفحة ${i} من ${pdf.numPages}...`, `${Math.round(50 + (i/pdf.numPages) * 20)}%`); |
|
|
|
|
|
await page.render({ |
|
|
canvasContext: context, |
|
|
viewport: viewport |
|
|
}).promise; |
|
|
|
|
|
documentPages.push({ |
|
|
pageNum: i, |
|
|
imageData: canvas.toDataURL('image/jpeg'), |
|
|
selected: true |
|
|
}); |
|
|
|
|
|
selectedPages.push(i-1); |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '70%'; |
|
|
updateLoadingProgress('جاري تحضير الصفحات للعرض...', '70%'); |
|
|
document.getElementById('pdfPagesCard').classList.remove('hidden'); |
|
|
|
|
|
displayPDFPages(); |
|
|
|
|
|
|
|
|
document.getElementById('progressBar').style.width = '80%'; |
|
|
updateLoadingProgress('جاري استخراج النص من الصفحات...', '80%'); |
|
|
await extractText(); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في معالجة ملف PDF:', error); |
|
|
alert('حدث خطأ أثناء معالجة ملف PDF'); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function processImage(file, targetType) { |
|
|
document.getElementById('statusText').textContent = 'جاري معالجة الصورة...'; |
|
|
document.getElementById('progressBar').style.width = '30%'; |
|
|
updateLoadingProgress('جاري معالجة الصورة...', '30%'); |
|
|
|
|
|
const reader = new FileReader(); |
|
|
reader.onload = async function(e) { |
|
|
const img = new Image(); |
|
|
img.onload = async function() { |
|
|
documentPages = [{ |
|
|
pageNum: 1, |
|
|
imageData: e.target.result, |
|
|
selected: true |
|
|
}]; |
|
|
selectedPages = [0]; |
|
|
|
|
|
document.getElementById('progressBar').style.width = '60%'; |
|
|
updateLoadingProgress('جاري تحضير الصورة للعرض والمعالجة...', '60%'); |
|
|
document.getElementById('pdfPagesCard').classList.remove('hidden'); |
|
|
|
|
|
displayPDFPages(); |
|
|
|
|
|
|
|
|
document.getElementById('progressBar').style.width = '80%'; |
|
|
updateLoadingProgress('جاري استخراج النص من الصورة...', '80%'); |
|
|
await extractText(); |
|
|
}; |
|
|
img.src = e.target.result; |
|
|
}; |
|
|
reader.onerror = function() { |
|
|
console.error('خطأ في قراءة الصورة'); |
|
|
alert('حدث خطأ أثناء قراءة الصورة'); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function processDocx(file, targetType) { |
|
|
try { |
|
|
document.getElementById('statusText').textContent = 'جاري معالجة ملف Word...'; |
|
|
document.getElementById('progressBar').style.width = '30%'; |
|
|
updateLoadingProgress('جاري معالجة ملف Word...', '30%'); |
|
|
|
|
|
const arrayBuffer = await file.arrayBuffer(); |
|
|
|
|
|
|
|
|
updateLoadingProgress('جاري استخراج النص من ملف Word...', '50%'); |
|
|
|
|
|
try { |
|
|
|
|
|
const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer }); |
|
|
|
|
|
document.getElementById('progressBar').style.width = '70%'; |
|
|
|
|
|
if (result && result.value) { |
|
|
let extractedText = result.value; |
|
|
|
|
|
updateLoadingProgress('تم استخراج النص بنجاح!', '80%'); |
|
|
|
|
|
|
|
|
if (selectedFileType === 'official') { |
|
|
updateLoadingProgress('جاري تصحيح المستند الرسمي...', '85%'); |
|
|
extractedText = await correctOfficialDocument(extractedText, targetType); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('progressBar').style.width = '90%'; |
|
|
updateLoadingProgress('جاري تحضير النتائج...', '90%'); |
|
|
|
|
|
|
|
|
displayDocxExtractedText(extractedText); |
|
|
|
|
|
|
|
|
if (targetType === 'source') { |
|
|
document.getElementById('sourceText').value = extractedText; |
|
|
addError('تم استخراج النص من ملف Word وإضافته كنص مصدر', 'info'); |
|
|
} else if (targetType === 'target') { |
|
|
document.getElementById('targetText').value = extractedText; |
|
|
addError('تم استخراج النص من ملف Word وإضافته كنص هدف', 'info'); |
|
|
} else if (targetType === 'extra') { |
|
|
document.getElementById('sourceExtraText').value = extractedText; |
|
|
addError('تم استخراج النص من ملف Word وإضافته كمصدر إضافي', 'info'); |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '100%'; |
|
|
updateLoadingProgress('تمت العملية بنجاح!', '100%'); |
|
|
|
|
|
setTimeout(() => { |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
}, 1000); |
|
|
} else { |
|
|
throw new Error('فشل في استخراج النص من الملف'); |
|
|
} |
|
|
} catch (mammothError) { |
|
|
console.error('خطأ في استخراج النص باستخدام Mammoth:', mammothError); |
|
|
|
|
|
|
|
|
updateLoadingProgress('جاري محاولة استخراج النص بطريقة بديلة...', '60%'); |
|
|
|
|
|
try { |
|
|
|
|
|
|
|
|
const docxContent = new Uint8Array(arrayBuffer); |
|
|
const blob = new Blob([docxContent], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); |
|
|
|
|
|
|
|
|
const fallbackText = await extractFallbackTextFromDocx(blob); |
|
|
|
|
|
if (fallbackText) { |
|
|
let extractedText = fallbackText; |
|
|
|
|
|
|
|
|
if (selectedFileType === 'official') { |
|
|
updateLoadingProgress('جاري تصحيح المستند الرسمي...', '85%'); |
|
|
extractedText = await correctOfficialDocument(extractedText, targetType); |
|
|
} |
|
|
|
|
|
|
|
|
displayDocxExtractedText(extractedText); |
|
|
|
|
|
|
|
|
if (targetType === 'source') { |
|
|
document.getElementById('sourceText').value = extractedText; |
|
|
addError('تم استخراج النص من ملف Word وإضافته كنص مصدر (باستخدام طريقة بديلة)', 'info'); |
|
|
} else if (targetType === 'target') { |
|
|
document.getElementById('targetText').value = extractedText; |
|
|
addError('تم استخراج النص من ملف Word وإضافته كنص هدف (باستخدام طريقة بديلة)', 'info'); |
|
|
} else if (targetType === 'extra') { |
|
|
document.getElementById('sourceExtraText').value = extractedText; |
|
|
addError('تم استخراج النص من ملف Word وإضافته كمصدر إضافي (باستخدام طريقة بديلة)', 'info'); |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '100%'; |
|
|
updateLoadingProgress('تمت العملية بنجاح!', '100%'); |
|
|
|
|
|
setTimeout(() => { |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
}, 1000); |
|
|
} else { |
|
|
throw new Error('فشل في استخراج النص بالطريقة البديلة'); |
|
|
} |
|
|
} catch (fallbackError) { |
|
|
console.error('خطأ في استخراج النص بالطريقة البديلة:', fallbackError); |
|
|
throw new Error('فشل في استخراج النص من الملف بكلتا الطريقتين'); |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('خطأ في معالجة ملف Word:', error); |
|
|
alert('حدث خطأ أثناء معالجة ملف Word: ' + error.message); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function extractFallbackTextFromDocx(blob) { |
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const arrayBuffer = await blob.arrayBuffer(); |
|
|
const data = new Uint8Array(arrayBuffer); |
|
|
|
|
|
|
|
|
const textParts = []; |
|
|
let currentText = ''; |
|
|
|
|
|
|
|
|
const arabicOrEnglishRegex = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\uFB50-\uFDFF\uFE70-\uFEFF\s\da-zA-Z.,;:'"!@#$%^&*()_+-=[\]{}|<>/?\\~`]+/g; |
|
|
|
|
|
|
|
|
const text = String.fromCharCode.apply(null, data); |
|
|
|
|
|
|
|
|
const matches = text.match(arabicOrEnglishRegex); |
|
|
|
|
|
if (matches && matches.length > 0) { |
|
|
|
|
|
return matches.join(' ').replace(/\s+/g, ' ').trim(); |
|
|
} |
|
|
|
|
|
return null; |
|
|
} catch (error) { |
|
|
console.error('خطأ في استخراج النص البديل:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displayDocxExtractedText(text) { |
|
|
|
|
|
document.getElementById('resultsCard').classList.remove('hidden'); |
|
|
document.getElementById('pdfPagesCard').classList.add('hidden'); |
|
|
|
|
|
document.getElementById('resultPreview').innerHTML = ` |
|
|
<div class="page-preview"> |
|
|
<h4>النص المستخرج من ملف Word${correctionApplied ? ' (مُصحح)' : ''}</h4> |
|
|
<div>${text.substring(0, 200)}${text.length > 200 ? '...' : ''}</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
document.getElementById('resultText').textContent = text; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function processExcel(file, targetType) { |
|
|
document.getElementById('statusText').textContent = 'جاري معالجة ملف Excel...'; |
|
|
document.getElementById('progressBar').style.width = '30%'; |
|
|
updateLoadingProgress('جاري معالجة ملف Excel...', '30%'); |
|
|
|
|
|
const reader = new FileReader(); |
|
|
|
|
|
reader.onload = function(e) { |
|
|
try { |
|
|
const data = new Uint8Array(e.target.result); |
|
|
excelWorkbook = XLSX.read(data, {type: 'array'}); |
|
|
|
|
|
|
|
|
document.getElementById('progressBar').style.width = '70%'; |
|
|
updateLoadingProgress('جاري استخراج البيانات من ملف Excel...', '70%'); |
|
|
|
|
|
if (excelWorkbook.SheetNames.length > 0) { |
|
|
|
|
|
renderSheetSelector(excelWorkbook.SheetNames, targetType); |
|
|
|
|
|
|
|
|
selectExcelSheet(excelWorkbook.SheetNames[0], targetType); |
|
|
|
|
|
updateLoadingProgress('تم استخراج البيانات بنجاح!', '90%'); |
|
|
|
|
|
|
|
|
useExcelContent(targetType); |
|
|
} else { |
|
|
alert('لم يتم العثور على أوراق في ملف Excel'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '100%'; |
|
|
updateLoadingProgress('تمت العملية بنجاح!', '100%'); |
|
|
setTimeout(() => { |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
}, 500); |
|
|
} catch (error) { |
|
|
console.error('خطأ في معالجة ملف Excel:', error); |
|
|
alert('حدث خطأ أثناء معالجة ملف Excel'); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
}; |
|
|
|
|
|
reader.onerror = function() { |
|
|
alert('حدث خطأ أثناء قراءة الملف'); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
}; |
|
|
|
|
|
reader.readAsArrayBuffer(file); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displayPDFPages() { |
|
|
const container = document.getElementById('pdfPagesContainer'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
documentPages.forEach((page, index) => { |
|
|
const pageDiv = document.createElement('div'); |
|
|
pageDiv.className = `pdf-page ${page.selected ? 'selected' : ''}`; |
|
|
pageDiv.dataset.index = index; |
|
|
|
|
|
const img = document.createElement('img'); |
|
|
img.src = page.imageData; |
|
|
img.alt = `Page ${page.pageNum}`; |
|
|
|
|
|
const pageNumDiv = document.createElement('div'); |
|
|
pageNumDiv.className = 'page-number'; |
|
|
pageNumDiv.textContent = page.pageNum; |
|
|
|
|
|
pageDiv.appendChild(img); |
|
|
pageDiv.appendChild(pageNumDiv); |
|
|
container.appendChild(pageDiv); |
|
|
|
|
|
pageDiv.addEventListener('click', function() { |
|
|
this.classList.toggle('selected'); |
|
|
documentPages[index].selected = !documentPages[index].selected; |
|
|
|
|
|
if (documentPages[index].selected) { |
|
|
if (!selectedPages.includes(index)) { |
|
|
selectedPages.push(index); |
|
|
} |
|
|
} else { |
|
|
const pos = selectedPages.indexOf(index); |
|
|
if (pos !== -1) { |
|
|
selectedPages.splice(pos, 1); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectAllPages() { |
|
|
selectedPages = []; |
|
|
documentPages.forEach((page, index) => { |
|
|
page.selected = true; |
|
|
selectedPages.push(index); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.pdf-page').forEach(pageDiv => { |
|
|
pageDiv.classList.add('selected'); |
|
|
}); |
|
|
} |
|
|
|
|
|
function deselectAllPages() { |
|
|
selectedPages = []; |
|
|
documentPages.forEach(page => { |
|
|
page.selected = false; |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.pdf-page').forEach(pageDiv => { |
|
|
pageDiv.classList.remove('selected'); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function extractText() { |
|
|
if (selectedPages.length === 0) { |
|
|
alert('الرجاء اختيار صفحة واحدة على الأقل'); |
|
|
return; |
|
|
} |
|
|
|
|
|
document.getElementById('processingStatus').classList.remove('hidden'); |
|
|
document.getElementById('statusText').textContent = 'جاري استخراج النص...'; |
|
|
document.getElementById('progressBar').style.width = '0%'; |
|
|
|
|
|
showLoadingIndicator('جاري تحضير الصفحات لاستخراج النص...', '0%'); |
|
|
|
|
|
extractedTexts = []; |
|
|
extractedPageNumbers = []; |
|
|
|
|
|
try { |
|
|
for (let i = 0; i < selectedPages.length; i++) { |
|
|
const pageIndex = selectedPages[i]; |
|
|
const pageData = documentPages[pageIndex]; |
|
|
|
|
|
document.getElementById('statusText').textContent = `جاري استخراج النص من الصفحة ${pageData.pageNum}...`; |
|
|
document.getElementById('progressBar').style.width = `${(i / selectedPages.length) * 70}%`; |
|
|
updateLoadingProgress(`جاري استخراج النص من الصفحة ${pageData.pageNum} (${i+1} من ${selectedPages.length})...`, |
|
|
`${Math.round((i / selectedPages.length) * 70)}%`); |
|
|
|
|
|
|
|
|
const extractedText = await extractTextFromImage(pageData.imageData, pageData.pageNum); |
|
|
extractedTexts.push(extractedText); |
|
|
extractedPageNumbers.push(pageData.pageNum); |
|
|
|
|
|
|
|
|
ocrPagesCount++; |
|
|
localStorage.setItem('ocrPagesCount', ocrPagesCount); |
|
|
document.getElementById('ocrCounter').textContent = ocrPagesCount; |
|
|
|
|
|
const now = new Date(); |
|
|
const dateStr = now.toLocaleDateString('ar-EG'); |
|
|
localStorage.setItem('lastOcrDate', dateStr); |
|
|
document.getElementById('lastOcrDate').textContent = dateStr; |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '80%'; |
|
|
updateLoadingProgress('جاري معالجة النصوص المستخرجة...', '80%'); |
|
|
|
|
|
|
|
|
let combinedText = extractedTexts.join('\n\n'); |
|
|
|
|
|
|
|
|
if (selectedFileType === 'official') { |
|
|
updateLoadingProgress('جاري تصحيح النص المستخرج للمستند الرسمي...', '85%'); |
|
|
combinedText = await correctOfficialDocument(combinedText, currentProcessingMode); |
|
|
} |
|
|
|
|
|
updateLoadingProgress('جاري تحضير النتائج...', '90%'); |
|
|
|
|
|
|
|
|
displayExtractedTexts(); |
|
|
|
|
|
|
|
|
if (currentProcessingMode === 'source') { |
|
|
document.getElementById('sourceText').value = combinedText; |
|
|
addError('تم إضافة النص المستخرج كنص مصدر بنجاح', 'info'); |
|
|
} else if (currentProcessingMode === 'target') { |
|
|
document.getElementById('targetText').value = combinedText; |
|
|
addError('تم إضافة النص المستخرج كنص هدف بنجاح', 'info'); |
|
|
} else if (currentProcessingMode === 'extra') { |
|
|
document.getElementById('sourceExtraText').value = combinedText; |
|
|
addError('تم إضافة النص المستخرج كمصدر إضافي بنجاح', 'info'); |
|
|
} |
|
|
|
|
|
updateLoadingProgress('تمت العملية بنجاح!', '100%'); |
|
|
document.getElementById('progressBar').style.width = '100%'; |
|
|
|
|
|
setTimeout(() => { |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
}, 1000); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في استخراج النص:', error); |
|
|
alert('حدث خطأ أثناء استخراج النص: ' + error.message); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function extractTextFromImage(imageData, pageNumber) { |
|
|
try { |
|
|
|
|
|
const response = await fetch(imageData); |
|
|
const blob = await response.blob(); |
|
|
|
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('image', blob); |
|
|
formData.append('language', 'ara'); |
|
|
|
|
|
|
|
|
const ocrResponse = await fetch(OCR_API_URL, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'x-rapidapi-key': RAPIDAPI_KEY, |
|
|
'x-rapidapi-host': 'ocr43.p.rapidapi.com' |
|
|
}, |
|
|
body: formData |
|
|
}); |
|
|
|
|
|
if (!ocrResponse.ok) { |
|
|
throw new Error(`فشل في طلب OCR: ${ocrResponse.status}`); |
|
|
} |
|
|
|
|
|
const data = await ocrResponse.json(); |
|
|
|
|
|
try { |
|
|
|
|
|
if (data && data.results && data.results[0] && data.results[0].entities && |
|
|
data.results[0].entities[0] && data.results[0].entities[0].objects && |
|
|
data.results[0].entities[0].objects[0] && data.results[0].entities[0].objects[0].entities && |
|
|
data.results[0].entities[0].objects[0].entities[0] && data.results[0].entities[0].objects[0].entities[0].text) { |
|
|
|
|
|
const text = data.results[0].entities[0].objects[0].entities[0].text; |
|
|
return text; |
|
|
} else { |
|
|
|
|
|
if (data && data.results && data.results[0] && data.results[0].text) { |
|
|
return data.results[0].text; |
|
|
} |
|
|
|
|
|
return `[لم يتم العثور على نص في الصفحة ${pageNumber}]`; |
|
|
} |
|
|
} catch (e) { |
|
|
console.error('Error parsing OCR response:', e); |
|
|
return `[خطأ في معالجة النص للصفحة ${pageNumber}]`; |
|
|
} |
|
|
} catch (error) { |
|
|
console.error(`Error in OCR for page ${pageNumber}:`, error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displayExtractedTexts() { |
|
|
document.getElementById('pdfPagesCard').classList.add('hidden'); |
|
|
document.getElementById('resultsCard').classList.remove('hidden'); |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
|
|
|
const resultPreview = document.getElementById('resultPreview'); |
|
|
resultPreview.innerHTML = ''; |
|
|
|
|
|
extractedTexts.forEach((text, index) => { |
|
|
const pagePreview = document.createElement('div'); |
|
|
pagePreview.className = 'page-preview'; |
|
|
|
|
|
pagePreview.innerHTML = ` |
|
|
<h4>الصفحة ${extractedPageNumbers[index]}${correctionApplied ? ' (مُصححة)' : ''}</h4> |
|
|
<div>${text.substring(0, 100)}${text.length > 100 ? '...' : ''}</div> |
|
|
`; |
|
|
|
|
|
resultPreview.appendChild(pagePreview); |
|
|
}); |
|
|
|
|
|
|
|
|
const finalText = correctionApplied ? correctedText : extractedTexts.join('\n\n'); |
|
|
document.getElementById('resultText').textContent = finalText; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function copyText() { |
|
|
const resultText = document.getElementById('resultText'); |
|
|
|
|
|
|
|
|
const textarea = document.createElement('textarea'); |
|
|
textarea.value = resultText.textContent; |
|
|
document.body.appendChild(textarea); |
|
|
|
|
|
|
|
|
textarea.select(); |
|
|
document.execCommand('copy'); |
|
|
|
|
|
|
|
|
document.body.removeChild(textarea); |
|
|
|
|
|
alert('تم نسخ النص'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function downloadText() { |
|
|
const text = document.getElementById('resultText').textContent; |
|
|
const blob = new Blob([text], { type: 'text/plain' }); |
|
|
const url = URL.createObjectURL(blob); |
|
|
|
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = 'extracted_text.txt'; |
|
|
a.click(); |
|
|
|
|
|
URL.revokeObjectURL(url); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function useOcrText(target) { |
|
|
const text = document.getElementById('resultText').textContent; |
|
|
|
|
|
if (target === 'source') { |
|
|
document.getElementById('sourceText').value = text; |
|
|
} else { |
|
|
document.getElementById('targetText').value = text; |
|
|
} |
|
|
|
|
|
const message = `تم استخدام النص المستخرج${correctionApplied ? ' المُصحح' : ''} كنص ${target === 'source' ? 'مصدر' : 'هدف'}`; |
|
|
alert(message); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function initializeImageEditor(index) { |
|
|
currentPageIndex = index; |
|
|
|
|
|
const imageCanvas = document.getElementById('imageCanvas'); |
|
|
const imageEditor = document.getElementById('imageEditor'); |
|
|
|
|
|
imageEditor.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
originalImageData = documentPages[index].imageData; |
|
|
|
|
|
|
|
|
if (fabricCanvas) { |
|
|
fabricCanvas.dispose(); |
|
|
} |
|
|
|
|
|
fabricCanvas = new fabric.Canvas('imageCanvas'); |
|
|
|
|
|
|
|
|
fabric.Image.fromURL(originalImageData, function(img) { |
|
|
|
|
|
const containerWidth = imageCanvas.parentElement.clientWidth; |
|
|
const scale = containerWidth / img.width; |
|
|
|
|
|
img.scale(scale); |
|
|
|
|
|
fabricCanvas.setWidth(img.width * scale); |
|
|
fabricCanvas.setHeight(img.height * scale); |
|
|
fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas)); |
|
|
}); |
|
|
} |
|
|
|
|
|
function rotateImageLeft() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
const img = fabricCanvas.backgroundImage; |
|
|
img.rotate((img.angle || 0) - 90); |
|
|
fabricCanvas.renderAll(); |
|
|
|
|
|
updateModifiedImage(); |
|
|
} |
|
|
|
|
|
function rotateImageRight() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
const img = fabricCanvas.backgroundImage; |
|
|
img.rotate((img.angle || 0) + 90); |
|
|
fabricCanvas.renderAll(); |
|
|
|
|
|
updateModifiedImage(); |
|
|
} |
|
|
|
|
|
function flipImageHorizontal() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
const img = fabricCanvas.backgroundImage; |
|
|
img.set('flipX', !img.flipX); |
|
|
fabricCanvas.renderAll(); |
|
|
|
|
|
updateModifiedImage(); |
|
|
} |
|
|
|
|
|
function flipImageVertical() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
const img = fabricCanvas.backgroundImage; |
|
|
img.set('flipY', !img.flipY); |
|
|
fabricCanvas.renderAll(); |
|
|
|
|
|
updateModifiedImage(); |
|
|
} |
|
|
|
|
|
function activateCropMode() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
if (isInCropMode) { |
|
|
return; |
|
|
} |
|
|
|
|
|
isInCropMode = true; |
|
|
document.getElementById('cropImage').textContent = 'تطبيق القص'; |
|
|
|
|
|
|
|
|
cropRect = new fabric.Rect({ |
|
|
left: 50, |
|
|
top: 50, |
|
|
width: fabricCanvas.width - 100, |
|
|
height: fabricCanvas.height - 100, |
|
|
fill: 'rgba(0,0,0,0.2)', |
|
|
stroke: 'black', |
|
|
strokeDashArray: [5, 5], |
|
|
strokeWidth: 2, |
|
|
selectable: true |
|
|
}); |
|
|
|
|
|
fabricCanvas.add(cropRect); |
|
|
fabricCanvas.setActiveObject(cropRect); |
|
|
} |
|
|
|
|
|
function applyCrop() { |
|
|
if (!fabricCanvas || !cropRect) return; |
|
|
|
|
|
isInCropMode = false; |
|
|
document.getElementById('cropImage').textContent = 'قص الصورة'; |
|
|
|
|
|
|
|
|
const rect = cropRect; |
|
|
const img = fabricCanvas.backgroundImage; |
|
|
|
|
|
|
|
|
fabricCanvas.remove(rect); |
|
|
|
|
|
|
|
|
const cropCanvas = document.createElement('canvas'); |
|
|
const cropContext = cropCanvas.getContext('2d'); |
|
|
|
|
|
cropCanvas.width = rect.getScaledWidth(); |
|
|
cropCanvas.height = rect.getScaledHeight(); |
|
|
|
|
|
|
|
|
const sourceLeft = rect.left; |
|
|
const sourceTop = rect.top; |
|
|
const sourceWidth = rect.getScaledWidth(); |
|
|
const sourceHeight = rect.getScaledHeight(); |
|
|
|
|
|
|
|
|
const tempImage = new Image(); |
|
|
tempImage.onload = function() { |
|
|
|
|
|
cropContext.drawImage( |
|
|
tempImage, |
|
|
sourceLeft / img.scaleX, |
|
|
sourceTop / img.scaleY, |
|
|
sourceWidth / img.scaleX, |
|
|
sourceHeight / img.scaleY, |
|
|
0, 0, |
|
|
cropCanvas.width, |
|
|
cropCanvas.height |
|
|
); |
|
|
|
|
|
|
|
|
fabric.Image.fromURL(cropCanvas.toDataURL(), function(newImg) { |
|
|
fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); |
|
|
fabricCanvas.setDimensions({ |
|
|
width: cropCanvas.width, |
|
|
height: cropCanvas.height |
|
|
}); |
|
|
|
|
|
updateModifiedImage(); |
|
|
}); |
|
|
}; |
|
|
tempImage.src = img._element.src; |
|
|
} |
|
|
|
|
|
function resetImage() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
|
|
|
fabric.Image.fromURL(originalImageData, function(img) { |
|
|
const containerWidth = document.getElementById('imageCanvas').parentElement.clientWidth; |
|
|
const scale = containerWidth / img.width; |
|
|
|
|
|
img.scale(scale); |
|
|
|
|
|
fabricCanvas.setWidth(img.width * scale); |
|
|
fabricCanvas.setHeight(img.height * scale); |
|
|
fabricCanvas.setBackgroundImage(img, fabricCanvas.renderAll.bind(fabricCanvas)); |
|
|
|
|
|
|
|
|
fabricCanvas.clear(); |
|
|
|
|
|
|
|
|
isInCropMode = false; |
|
|
cropRect = null; |
|
|
document.getElementById('cropImage').textContent = 'قص الصورة'; |
|
|
|
|
|
|
|
|
updateModifiedImage(); |
|
|
}); |
|
|
} |
|
|
|
|
|
function improveContrast() { |
|
|
if (!fabricCanvas) return; |
|
|
|
|
|
const img = fabricCanvas.backgroundImage; |
|
|
|
|
|
|
|
|
const tempCanvas = document.createElement('canvas'); |
|
|
const tempContext = tempCanvas.getContext('2d'); |
|
|
|
|
|
tempCanvas.width = img.width * img.scaleX; |
|
|
tempCanvas.height = img.height * img.scaleY; |
|
|
|
|
|
|
|
|
tempContext.drawImage(img._element, 0, 0, tempCanvas.width, tempCanvas.height); |
|
|
|
|
|
|
|
|
const imageData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height); |
|
|
const data = imageData.data; |
|
|
|
|
|
|
|
|
const factor = 1.5; |
|
|
|
|
|
for (let i = 0; i < data.length; i += 4) { |
|
|
|
|
|
const r = data[i]; |
|
|
const g = data[i + 1]; |
|
|
const b = data[i + 2]; |
|
|
|
|
|
|
|
|
data[i] = Math.min(255, Math.max(0, factor * (r - 128) + 128)); |
|
|
data[i + 1] = Math.min(255, Math.max(0, factor * (g - 128) + 128)); |
|
|
data[i + 2] = Math.min(255, Math.max(0, factor * (b - 128) + 128)); |
|
|
} |
|
|
|
|
|
|
|
|
tempContext.putImageData(imageData, 0, 0); |
|
|
|
|
|
|
|
|
fabric.Image.fromURL(tempCanvas.toDataURL(), function(newImg) { |
|
|
fabricCanvas.setBackgroundImage(newImg, fabricCanvas.renderAll.bind(fabricCanvas)); |
|
|
|
|
|
updateModifiedImage(); |
|
|
}); |
|
|
} |
|
|
|
|
|
function updateModifiedImage() { |
|
|
|
|
|
if (fabricCanvas && currentPageIndex !== -1) { |
|
|
documentPages[currentPageIndex].imageData = fabricCanvas.toDataURL(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderSheetSelector(sheetNames, targetType) { |
|
|
const container = document.getElementById('sheetSelectorContainer'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
sheetNames.forEach(sheetName => { |
|
|
const btn = document.createElement('div'); |
|
|
btn.className = 'sheet-selector'; |
|
|
btn.textContent = sheetName; |
|
|
btn.onclick = function() { |
|
|
document.querySelectorAll('.sheet-selector').forEach(b => b.classList.remove('active')); |
|
|
this.classList.add('active'); |
|
|
selectExcelSheet(sheetName, targetType); |
|
|
}; |
|
|
container.appendChild(btn); |
|
|
}); |
|
|
|
|
|
|
|
|
const firstSheet = container.querySelector('.sheet-selector'); |
|
|
if (firstSheet) { |
|
|
firstSheet.classList.add('active'); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('excelPreviewCard').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function selectExcelSheet(sheetName, targetType) { |
|
|
currentSheetName = sheetName; |
|
|
|
|
|
|
|
|
const worksheet = excelWorkbook.Sheets[sheetName]; |
|
|
const data = XLSX.utils.sheet_to_json(worksheet, {header: 1}); |
|
|
|
|
|
|
|
|
excelData = { |
|
|
targetType: targetType, |
|
|
sheetName: sheetName, |
|
|
data: data |
|
|
}; |
|
|
|
|
|
|
|
|
renderExcelTable(data); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderExcelTable(data) { |
|
|
const container = document.getElementById('excelContent'); |
|
|
|
|
|
if (!data || data.length === 0) { |
|
|
container.innerHTML = '<p class="text-center p-4 text-gray-500">لا توجد بيانات في هذه الورقة</p>'; |
|
|
return; |
|
|
} |
|
|
|
|
|
let html = '<table class="preview-table"><thead><tr>'; |
|
|
|
|
|
|
|
|
const headerRow = data[0]; |
|
|
|
|
|
for (let i = 0; i < headerRow.length; i++) { |
|
|
html += `<th>${headerRow[i] || 'عمود ' + (i+1)}</th>`; |
|
|
} |
|
|
|
|
|
html += '</tr></thead><tbody>'; |
|
|
|
|
|
|
|
|
for (let i = 1; i < data.length; i++) { |
|
|
html += '<tr>'; |
|
|
const row = data[i]; |
|
|
|
|
|
for (let j = 0; j < headerRow.length; j++) { |
|
|
html += `<td>${row[j] !== undefined ? row[j] : ''}</td>`; |
|
|
} |
|
|
|
|
|
html += '</tr>'; |
|
|
} |
|
|
|
|
|
html += '</tbody></table>'; |
|
|
container.innerHTML = html; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function useExcelContent(target) { |
|
|
if (!excelData || !excelData.data) { |
|
|
alert('لم يتم تحميل بيانات Excel'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
let textContent = ''; |
|
|
|
|
|
|
|
|
for (let i = 1; i < excelData.data.length; i++) { |
|
|
const row = excelData.data[i]; |
|
|
if (row && row.length > 0) { |
|
|
textContent += row.join('\t') + '\n'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (target === 'source') { |
|
|
document.getElementById('sourceText').value = textContent; |
|
|
} else if (target === 'target') { |
|
|
document.getElementById('targetText').value = textContent; |
|
|
} else if (target === 'extra') { |
|
|
document.getElementById('sourceExtraText').value = textContent; |
|
|
} |
|
|
|
|
|
alert('تم استخدام محتوى Excel بنجاح'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeNumbers(text) { |
|
|
|
|
|
return text.replace(/[٠١٢٣٤٥٦٧٨٩]/g, d => d.charCodeAt(0) - 1632) |
|
|
.replace(/[۰۱۲۳۴۵۶۷۸۹]/g, d => d.charCodeAt(0) - 1776); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function splitIntoSentences(text) { |
|
|
|
|
|
const sentences = text.split(/(?<=[.!?])\s+|(?<=\n\s*\n)/g); |
|
|
|
|
|
|
|
|
return sentences.filter(s => s.trim()); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('submitBtn').addEventListener('click', async () => { |
|
|
try { |
|
|
let sourceText = document.getElementById('sourceText').value; |
|
|
let targetText = document.getElementById('targetText').value; |
|
|
|
|
|
|
|
|
document.getElementById('errorsList').innerHTML = ''; |
|
|
document.getElementById('resultSection').classList.remove('hidden'); |
|
|
|
|
|
if (!sourceText || !targetText) { |
|
|
addError('يرجى إدخال كلا النصين المصدر والهدف'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
showLoadingIndicator('جاري تحضير النصوص للتحليل...', '0%'); |
|
|
|
|
|
|
|
|
if (selectedFileType === 'official') { |
|
|
addError('جارٍ تصحيح النصوص للمستندات الرسمية...', 'info'); |
|
|
updateLoadingProgress('جاري تصحيح النص المصدر...', '5%'); |
|
|
|
|
|
|
|
|
if (!correctionApplied) { |
|
|
sourceText = await correctOfficialDocument(sourceText, 'source'); |
|
|
document.getElementById('sourceText').value = sourceText; |
|
|
} |
|
|
|
|
|
updateLoadingProgress('جاري تصحيح النص الهدف...', '20%'); |
|
|
|
|
|
|
|
|
targetText = await correctOfficialDocument(targetText, 'target'); |
|
|
document.getElementById('targetText').value = targetText; |
|
|
|
|
|
addError('تم تصحيح النصوص بنجاح', 'info'); |
|
|
} |
|
|
|
|
|
addError('جارٍ تقسيم النصوص ومزامنتها...', 'info'); |
|
|
updateLoadingProgress('جاري تقسيم النصوص ومزامنتها...', '30%'); |
|
|
|
|
|
|
|
|
const segments = await alignTextsWithModel(sourceText, targetText); |
|
|
|
|
|
if (!segments || segments.length === 0) { |
|
|
throw new Error('فشل في تقسيم النصوص - لم يتم العثور على أقسام'); |
|
|
} |
|
|
|
|
|
|
|
|
displayDraftSegments(segments); |
|
|
|
|
|
|
|
|
analysisSegments = segments.map(segment => ({ |
|
|
source: segment.source, |
|
|
target: segment.target, |
|
|
analysis: 'جارٍ التحليل...', |
|
|
errors: { numbers: 0, missing: 0, meaning: 0 } |
|
|
})); |
|
|
|
|
|
addError(`تم تقسيم النصوص إلى ${segments.length} قسم بنجاح`, 'info'); |
|
|
addError('جارٍ تحليل الأقسام...', 'info'); |
|
|
|
|
|
updateLoadingProgress('جاري تحليل الأقسام...', '50%'); |
|
|
|
|
|
let totalNumberErrors = 0; |
|
|
let totalMissingErrors = 0; |
|
|
let totalMeaningErrors = 0; |
|
|
|
|
|
|
|
|
for (let i = 0; i < segments.length; i++) { |
|
|
updateLoadingProgress(`جاري تحليل القسم ${i+1} من ${segments.length}...`, |
|
|
`${Math.round(50 + ((i + 1) / segments.length) * 40)}%`); |
|
|
|
|
|
|
|
|
const analysisResult = await analyzeAlignedPair(segments[i].source, segments[i].target, i+1); |
|
|
|
|
|
|
|
|
updateSegmentAnalysis(i, analysisResult); |
|
|
|
|
|
|
|
|
analysisSegments[i].analysis = analysisResult.analysis; |
|
|
analysisSegments[i].errors = analysisResult.errors; |
|
|
|
|
|
|
|
|
totalNumberErrors += analysisResult.errors.numbers; |
|
|
totalMissingErrors += analysisResult.errors.missing; |
|
|
totalMeaningErrors += analysisResult.errors.meaning; |
|
|
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 200)); |
|
|
} |
|
|
|
|
|
updateLoadingProgress('جاري تحضير النتائج النهائية...', '95%'); |
|
|
|
|
|
|
|
|
const totalErrors = totalNumberErrors + totalMissingErrors + totalMeaningErrors; |
|
|
if (totalErrors === 0) { |
|
|
addError('لم يتم العثور على أخطاء - النصوص متطابقة', 'info'); |
|
|
} else { |
|
|
addError(`تم الانتهاء من التحليل. العثور على ${totalErrors} خطأ:`, 'info'); |
|
|
if (totalNumberErrors > 0) addError(`- ${totalNumberErrors} اختلاف في الأرقام`, 'warning'); |
|
|
if (totalMissingErrors > 0) addError(`- ${totalMissingErrors} نص مفقود`, 'warning'); |
|
|
if (totalMeaningErrors > 0) addError(`- ${totalMeaningErrors} اختلاف في المعنى`, 'warning'); |
|
|
} |
|
|
|
|
|
|
|
|
displaySegmentedView(); |
|
|
|
|
|
|
|
|
setupInteractiveView(); |
|
|
|
|
|
updateLoadingProgress('تمت العملية بنجاح!', '100%'); |
|
|
setTimeout(() => { |
|
|
hideLoadingIndicator(); |
|
|
}, 1000); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error during analysis:', error); |
|
|
addError('حدث خطأ أثناء التحليل: ' + error.message, 'error'); |
|
|
hideLoadingIndicator(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addPreliminaryHighlights(sourceText, targetText) { |
|
|
let highlightedText = sourceText; |
|
|
|
|
|
|
|
|
const numberRegex = /(\d+|[٠١٢٣٤٥٦٧٨٩]+)/g; |
|
|
highlightedText = highlightedText.replace(numberRegex, '<span class="preliminary-highlight-number">\$1</span>'); |
|
|
|
|
|
|
|
|
const paragraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim().length > 20); |
|
|
|
|
|
for (const paragraph of paragraphs) { |
|
|
if (!isTextSubstantiallyIncluded(paragraph, targetText)) { |
|
|
highlightedText = highlightedText.replace( |
|
|
paragraph, |
|
|
`<span class="preliminary-highlight-missing">${paragraph}</span>` |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
return highlightedText; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isTextSubstantiallyIncluded(text, targetText) { |
|
|
|
|
|
const words = text.split(/\s+/).filter(w => w.length > 3); |
|
|
if (words.length === 0) return true; |
|
|
|
|
|
|
|
|
const foundCount = words.filter(word => targetText.includes(word)).length; |
|
|
|
|
|
|
|
|
return foundCount / words.length > 0.5; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function alignTextsWithModel(sourceText, targetText) { |
|
|
try { |
|
|
|
|
|
document.getElementById('processingStatus').classList.remove('hidden'); |
|
|
document.getElementById('statusText').textContent = 'جاري تقسيم ومزامنة النصوص...'; |
|
|
document.getElementById('progressBar').style.width = '30%'; |
|
|
|
|
|
updateLoadingProgress('جاري تقسيم ومزامنة النصوص...', '35%'); |
|
|
|
|
|
|
|
|
const prompt = `مهمتك هي تقسيم النصين التاليين (النص المصدر والنص الهدف) إلى أقسام متوازية بحيث يتطابق كل قسم في المصدر مع ما يقابله في الهدف. |
|
|
|
|
|
ملاحظات مهمة: |
|
|
1. عدد الأقسام يجب أن لا يتجاوز 6 أقسام بأي حال |
|
|
2. قسّم النص إلى أجزاء منطقية ومترابطة |
|
|
3. حاول الحفاظ على تكامل الفقرات والجمل |
|
|
|
|
|
أعِد النتائج بصيغة JSON بهذا الشكل: |
|
|
{ |
|
|
"segments": [ |
|
|
{ |
|
|
"source": "نص المصدر للقسم الأول", |
|
|
"target": "نص الهدف المقابل للقسم الأول" |
|
|
}, |
|
|
... |
|
|
] |
|
|
} |
|
|
|
|
|
النص المصدر: |
|
|
${sourceText} |
|
|
|
|
|
النص الهدف: |
|
|
${targetText}`; |
|
|
|
|
|
const payload = { |
|
|
model: "deepseek-chat", |
|
|
messages: [ |
|
|
{ role: "system", content: "أنت خبير في تقسيم النصوص ومزامنتها للمقارنة والتحليل." }, |
|
|
{ role: "user", content: prompt } |
|
|
], |
|
|
temperature: 0.3, |
|
|
max_tokens: 8000 |
|
|
}; |
|
|
|
|
|
document.getElementById('progressBar').style.width = '50%'; |
|
|
updateLoadingProgress('جاري معالجة تقسيم النصوص...', '40%'); |
|
|
|
|
|
const response = await fetch(DEEPSEEK_API_URL, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY, |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error('فشل استدعاء نموذج : ' + response.statusText); |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '80%'; |
|
|
updateLoadingProgress('جاري تحليل النتائج...', '45%'); |
|
|
|
|
|
const data = await response.json(); |
|
|
const result = data.choices[0].message.content; |
|
|
|
|
|
|
|
|
let segments = []; |
|
|
try { |
|
|
const jsonMatch = result.match(/\{[\s\S]*\}/); |
|
|
if (jsonMatch) { |
|
|
const parsedData = JSON.parse(jsonMatch[0]); |
|
|
segments = parsedData.segments || []; |
|
|
} |
|
|
} catch (e) { |
|
|
console.error('خطأ في تحليل نتيجة تقسيم النصوص:', e); |
|
|
|
|
|
segments = createFallbackSegments(sourceText, targetText); |
|
|
} |
|
|
|
|
|
|
|
|
if (segments.length > 6) { |
|
|
segments = consolidateSegments(segments, 6); |
|
|
} |
|
|
|
|
|
document.getElementById('progressBar').style.width = '100%'; |
|
|
document.getElementById('statusText').textContent = 'تم تقسيم النصوص بنجاح!'; |
|
|
|
|
|
updateLoadingProgress('تم تقسيم النصوص بنجاح!', '50%'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
}, 1000); |
|
|
|
|
|
return segments; |
|
|
} catch (error) { |
|
|
console.error('خطأ في تقسيم النصوص:', error); |
|
|
|
|
|
document.getElementById('processingStatus').classList.add('hidden'); |
|
|
addError('حدث خطأ أثناء تقسيم النصوص: ' + error.message); |
|
|
|
|
|
|
|
|
return createFallbackSegments(sourceText, targetText); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function consolidateSegments(segments, maxSegments) { |
|
|
if (segments.length <= maxSegments) { |
|
|
return segments; |
|
|
} |
|
|
|
|
|
|
|
|
const segmentsPerGroup = Math.ceil(segments.length / maxSegments); |
|
|
const consolidatedSegments = []; |
|
|
|
|
|
for (let i = 0; i < segments.length; i += segmentsPerGroup) { |
|
|
|
|
|
const groupSegments = segments.slice(i, Math.min(i + segmentsPerGroup, segments.length)); |
|
|
|
|
|
const consolidatedSource = groupSegments.map(s => s.source).join('\n\n'); |
|
|
const consolidatedTarget = groupSegments.map(s => s.target).join('\n\n'); |
|
|
|
|
|
consolidatedSegments.push({ |
|
|
source: consolidatedSource, |
|
|
target: consolidatedTarget |
|
|
}); |
|
|
} |
|
|
|
|
|
return consolidatedSegments; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createFallbackSegments(sourceText, targetText) { |
|
|
addError('تم الانتقال إلى آلية التقسيم الاحتياطية', 'warning'); |
|
|
|
|
|
|
|
|
const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); |
|
|
const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim()); |
|
|
|
|
|
|
|
|
const MAX_SEGMENTS = 6; |
|
|
const sourceSegmentsCount = Math.min(MAX_SEGMENTS, sourceParagraphs.length); |
|
|
const segmentsPerGroup = Math.ceil(sourceParagraphs.length / sourceSegmentsCount); |
|
|
|
|
|
const segments = []; |
|
|
|
|
|
|
|
|
for (let i = 0; i < sourceSegmentsCount; i++) { |
|
|
const startIdx = i * segmentsPerGroup; |
|
|
const endIdx = Math.min(startIdx + segmentsPerGroup, sourceParagraphs.length); |
|
|
|
|
|
const sourceSegment = sourceParagraphs.slice(startIdx, endIdx).join('\n\n'); |
|
|
|
|
|
|
|
|
let targetSegment = ''; |
|
|
|
|
|
|
|
|
const targetStartIdx = Math.floor((startIdx / sourceParagraphs.length) * targetParagraphs.length); |
|
|
const targetEndIdx = Math.min(Math.floor((endIdx / sourceParagraphs.length) * targetParagraphs.length), targetParagraphs.length); |
|
|
|
|
|
targetSegment = targetParagraphs.slice(targetStartIdx, targetEndIdx).join('\n\n'); |
|
|
|
|
|
|
|
|
if (!targetSegment) { |
|
|
targetSegment = '(لا يوجد نص مقابل في الهدف)'; |
|
|
} |
|
|
|
|
|
segments.push({ |
|
|
source: sourceSegment, |
|
|
target: targetSegment |
|
|
}); |
|
|
} |
|
|
|
|
|
return segments; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function analyzeAlignedPair(sourceText, targetText, pairNumber) { |
|
|
try { |
|
|
|
|
|
document.getElementById('statusText').textContent = `جاري تحليل القسم ${pairNumber}...`; |
|
|
|
|
|
updateLoadingProgress(`جاري تحليل القسم ${pairNumber}...`, |
|
|
`${Math.round(50 + ((pairNumber) / analysisSegments.length) * 40)}%`); |
|
|
|
|
|
|
|
|
const prompt = `قارن النص المصدر والنص الهدف التاليين، وحدد بدقة: |
|
|
1. اختلافات الأرقام: ضع الرقم المختلف بين علامتي < > مع الكلمة السابقة والكلمة اللاحقة أو الجملة كاملة |
|
|
2. النصوص المفقودة: ضع النص المفقود في الترجمة بين علامتي __ __ |
|
|
3. اختلافات المعنى: ضع النص الذي تغير معناه بين علامتي [MEANING] [/MEANING] |
|
|
|
|
|
اعتبر الأرقام بمختلف أنظمتها (العربية والهندية والإنجليزية) متطابقة إذا كانت تمثل نفس الرقم. |
|
|
الجمل عادةً تكون من 20 إلى 300 كلمة. النصوص عبارة عن نص سورس ونص تاجرت واحد أصلي والثاني ترجمته. |
|
|
استخراج النصوص المفقودة حيث يوجد في المصدر جملة أو فقرة أو كلمة (بالعربي أو الإنجليزي) وفي النص المقابل لا يوجد ما يعادلها في المعنى بأي لغة. |
|
|
قم بتحليل كل جملة على حدة وتحديد الاختلافات بدقة. |
|
|
|
|
|
لكل اختلاف، قدم شرحاً موجزاً للخطأ والتصحيح المقترح. |
|
|
|
|
|
النص المصدر: |
|
|
${sourceText} |
|
|
|
|
|
النص الهدف: |
|
|
${targetText} |
|
|
`; |
|
|
|
|
|
const payload = { |
|
|
model: "deepseek-chat", |
|
|
messages: [ |
|
|
{ role: "system", content: "أنت خبير لغوي في تحليل ومقارنة النصوص المترجمة بدقة عالية." }, |
|
|
{ role: "user", content: prompt } |
|
|
], |
|
|
temperature: 0.2, |
|
|
max_tokens: 2048 |
|
|
}; |
|
|
|
|
|
|
|
|
const response = await fetch(DEEPSEEK_API_URL, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Authorization': 'Bearer ' + DEEPSEEK_API_KEY, |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error('فشل استدعاء API للتحليل: ' + response.statusText); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
const analysisResult = data.choices[0].message.content.trim(); |
|
|
|
|
|
|
|
|
const numberDiffs = (analysisResult.match(/<[^<>]+>/g) || []).length; |
|
|
const missingTexts = (analysisResult.match(/__(.*?)__/g) || []).length; |
|
|
const meaningDiffs = (analysisResult.match(/$$MEANING$$(.*?)$$\/MEANING$$/g) || []).length; |
|
|
|
|
|
const totalDiffs = numberDiffs + missingTexts + meaningDiffs; |
|
|
|
|
|
|
|
|
let summary; |
|
|
if (totalDiffs === 0) { |
|
|
summary = '<span class="perfect-match">لم يتم العثور على اختلافات. النصوص متطابقة.</span>'; |
|
|
} else { |
|
|
summary = `تم العثور على `; |
|
|
const parts = []; |
|
|
if (numberDiffs > 0) parts.push(`<span class="highlight-number">${numberDiffs} اختلاف في الأرقام</span>`); |
|
|
if (missingTexts > 0) parts.push(`<span class="highlight-missing">${missingTexts} نص مفقود</span>`); |
|
|
if (meaningDiffs > 0) parts.push(`<span class="highlight-meaning">${meaningDiffs} اختلاف في المعنى</span>`); |
|
|
summary += parts.join('، ') + "."; |
|
|
} |
|
|
|
|
|
return { |
|
|
pairId: pairNumber, |
|
|
sourceText: sourceText, |
|
|
targetText: targetText, |
|
|
analysis: analysisResult, |
|
|
summary: summary, |
|
|
errors: { |
|
|
numbers: numberDiffs, |
|
|
missing: missingTexts, |
|
|
meaning: meaningDiffs |
|
|
} |
|
|
}; |
|
|
} catch (error) { |
|
|
console.error(`خطأ في تحليل القسم ${pairNumber}:`, error); |
|
|
return { |
|
|
pairId: pairNumber, |
|
|
sourceText: sourceText, |
|
|
targetText: targetText, |
|
|
analysis: "حدث خطأ في التحليل: " + error.message, |
|
|
summary: "فشل التحليل", |
|
|
errors: { numbers: 0, missing: 0, meaning: 0 } |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displayDraftSegments(segments) { |
|
|
const container = document.getElementById('paragraphDivisionsContainer'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
segments.forEach((segment, index) => { |
|
|
|
|
|
const sectionDiv = document.createElement('div'); |
|
|
sectionDiv.className = 'collapsible-section'; |
|
|
|
|
|
|
|
|
const headerDiv = document.createElement('div'); |
|
|
headerDiv.className = 'section-header'; |
|
|
headerDiv.innerHTML = ` |
|
|
<div class="flex items-center"> |
|
|
<span class="draft-marker">مسودة</span> |
|
|
<span>القسم ${index+1}</span> |
|
|
</div> |
|
|
<i class="fas fa-chevron-down"></i> |
|
|
`; |
|
|
|
|
|
|
|
|
const preliminaryHighlightedSource = addPreliminaryHighlights(segment.source, segment.target); |
|
|
|
|
|
|
|
|
const contentDiv = document.createElement('div'); |
|
|
contentDiv.className = 'section-content'; |
|
|
|
|
|
|
|
|
contentDiv.innerHTML = ` |
|
|
<div class="aligned-paragraphs mb-4"> |
|
|
<div class="paragraph-ar">${preliminaryHighlightedSource}</div> |
|
|
<div class="paragraph-en">${segment.target}</div> |
|
|
</div> |
|
|
<div id="analysis-segment-${index}" class="paragraph-section"> |
|
|
<h4 class="font-bold text-gray-700 mb-2">التحليل:</h4> |
|
|
<div class="bg-gray-50 p-3 rounded-lg flex items-center justify-center"> |
|
|
<div class="animate-spin h-5 w-5 border-4 border-blue-600 rounded-full border-t-transparent ml-2"></div> |
|
|
<span>جارٍ التحليل...</span> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
sectionDiv.appendChild(headerDiv); |
|
|
sectionDiv.appendChild(contentDiv); |
|
|
container.appendChild(sectionDiv); |
|
|
|
|
|
|
|
|
headerDiv.addEventListener('click', function() { |
|
|
contentDiv.classList.toggle('open'); |
|
|
const icon = headerDiv.querySelector('i.fas'); |
|
|
icon.classList.toggle('fa-chevron-down'); |
|
|
icon.classList.toggle('fa-chevron-up'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('fullTextDraftSection').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateSegmentAnalysis(segmentIndex, analysisResult) { |
|
|
const analysisContainer = document.getElementById(`analysis-segment-${segmentIndex}`); |
|
|
if (analysisContainer) { |
|
|
|
|
|
analysisContainer.innerHTML = ` |
|
|
<h4 class="font-bold text-gray-700 mb-2">التحليل:</h4> |
|
|
<div class="bg-gray-50 p-3 rounded-lg"> |
|
|
${formatAnalysisText(analysisResult.analysis)} |
|
|
</div> |
|
|
<div class="mt-3 p-3 rounded-lg bg-blue-50 border-r-4 border-blue-400"> |
|
|
<span class="font-bold block mb-1">الملخص:</span> ${analysisResult.summary} |
|
|
</div> |
|
|
<div class="mt-3 grid grid-cols-3 gap-2 text-sm"> |
|
|
<div class="bg-yellow-50 p-2 rounded-lg border border-yellow-200 flex flex-col items-center"> |
|
|
<span class="font-bold">اختلافات الأرقام</span> |
|
|
<span class="text-xl font-bold mt-1 ${analysisResult.errors.numbers > 0 ? 'text-yellow-600' : 'text-green-600'}"> |
|
|
${analysisResult.errors.numbers} |
|
|
</span> |
|
|
</div> |
|
|
<div class="bg-blue-50 p-2 rounded-lg border border-blue-200 flex flex-col items-center"> |
|
|
<span class="font-bold">نصوص مفقودة</span> |
|
|
<span class="text-xl font-bold mt-1 ${analysisResult.errors.missing > 0 ? 'text-blue-600' : 'text-green-600'}"> |
|
|
${analysisResult.errors.missing} |
|
|
</span> |
|
|
</div> |
|
|
<div class="bg-red-50 p-2 rounded-lg border border-red-200 flex flex-col items-center"> |
|
|
<span class="font-bold">اختلافات المعنى</span> |
|
|
<span class="text-xl font-bold mt-1 ${analysisResult.errors.meaning > 0 ? 'text-red-600' : 'text-green-600'}"> |
|
|
${analysisResult.errors.meaning} |
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyHighlights(originalText, targetText, analysisOutput) { |
|
|
|
|
|
const sentences = splitIntoSentences(originalText); |
|
|
|
|
|
|
|
|
const enhancedSourceText = highlightMissingText(originalText, targetText); |
|
|
|
|
|
|
|
|
const numberMatches = Array.from(analysisOutput.matchAll(/<([^<>]+)>/g)).map(m => m[1].trim()); |
|
|
const missingMatches = Array.from(analysisOutput.matchAll(/__(.*?)__/g)).map(m => m[1].trim()); |
|
|
const meaningMatches = Array.from(analysisOutput.matchAll(/$$MEANING$$(.*?)$$\/MEANING$$/g)).map(m => m[1].trim()); |
|
|
|
|
|
|
|
|
const highlightedText = sentences.map((sentence, index) => { |
|
|
let hasError = false; |
|
|
let highlightedSentence = sentence; |
|
|
let errorType = ''; |
|
|
|
|
|
|
|
|
for (const phrase of numberMatches) { |
|
|
if (sentence.includes(phrase)) { |
|
|
const regex = new RegExp(escapeRegExp(phrase), 'g'); |
|
|
highlightedSentence = highlightedSentence.replace(regex, `<span class="highlight-number" data-error-type="number" data-explanation="تأكد من صحة الرقم في النص الهدف وتطابقه مع النص المصدر">${phrase}</span>`); |
|
|
hasError = true; |
|
|
errorType = 'number'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const phrase of missingMatches) { |
|
|
if (sentence.includes(phrase)) { |
|
|
const regex = new RegExp(escapeRegExp(phrase), 'g'); |
|
|
highlightedSentence = highlightedSentence.replace(regex, `<span class="highlight-missing" data-error-type="missing" data-explanation="أضف النص المفقود إلى الترجمة للحفاظ على اكتمال المعنى">${phrase}</span>`); |
|
|
hasError = true; |
|
|
errorType = errorType || 'missing'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const phrase of meaningMatches) { |
|
|
if (sentence.includes(phrase)) { |
|
|
const regex = new RegExp(escapeRegExp(phrase), 'g'); |
|
|
highlightedSentence = highlightedSentence.replace(regex, `<span class="highlight-meaning" data-error-type="meaning" data-explanation="راجع الترجمة للتأكد من نقل المعنى الصحيح دون تحريف">${phrase}</span>`); |
|
|
hasError = true; |
|
|
errorType = errorType || 'meaning'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (hasError) { |
|
|
return `<span class="sentence-with-error" data-error-id="${index}" data-error-type="${errorType}" data-sentence-number="${index+1}">${highlightedSentence}</span>`; |
|
|
} |
|
|
|
|
|
return highlightedSentence; |
|
|
}).join(' '); |
|
|
|
|
|
return highlightedText; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function highlightMissingText(sourceText, targetText) { |
|
|
|
|
|
const sourceParagraphs = sourceText.split(/\n\s*\n/).filter(p => p.trim()); |
|
|
const targetParagraphs = targetText.split(/\n\s*\n/).filter(p => p.trim()); |
|
|
|
|
|
|
|
|
let modifiedSource = sourceText; |
|
|
|
|
|
|
|
|
for (const paragraph of sourceParagraphs) { |
|
|
if (paragraph.trim().length < 10) continue; |
|
|
|
|
|
|
|
|
let isCompletelyMissing = true; |
|
|
|
|
|
|
|
|
for (const targetPara of targetParagraphs) { |
|
|
if (targetPara.includes(paragraph) || paragraph.includes(targetPara)) { |
|
|
isCompletelyMissing = false; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (isCompletelyMissing) { |
|
|
modifiedSource = modifiedSource.replace( |
|
|
paragraph, |
|
|
`<span class="completely-missing" title="هذا النص مفقود تماما في الترجمة">${paragraph}</span>` |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const sourceLines = sourceParagraphs.join('\n').split(/[.!?]\s+/); |
|
|
|
|
|
for (const line of sourceLines) { |
|
|
if (line.trim().length < 5) continue; |
|
|
|
|
|
|
|
|
let isPartiallyMissing = true; |
|
|
|
|
|
if (targetText.includes(line)) { |
|
|
isPartiallyMissing = false; |
|
|
} else { |
|
|
|
|
|
const keywords = line.split(/\s+/).filter(word => word.length > 3); |
|
|
let foundKeywords = 0; |
|
|
|
|
|
for (const keyword of keywords) { |
|
|
if (targetText.includes(keyword)) { |
|
|
foundKeywords++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (keywords.length > 0 && foundKeywords / keywords.length > 0.5) { |
|
|
isPartiallyMissing = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (isPartiallyMissing && !modifiedSource.includes(`<span class="completely-missing"`)) { |
|
|
modifiedSource = modifiedSource.replace( |
|
|
line, |
|
|
`<span class="partially-missing" title="هذا النص موجود بشكل جزئي في الترجمة">${line}</span>` |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
return modifiedSource; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function displaySegmentedView() { |
|
|
const container = document.getElementById('segmentedComparisonContainer'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
|
|
|
analysisSegments.forEach((segment, index) => { |
|
|
|
|
|
const hasNumbers = segment.errors.numbers > 0; |
|
|
const hasMissing = segment.errors.missing > 0; |
|
|
const hasMeaning = segment.errors.meaning > 0; |
|
|
|
|
|
|
|
|
let tagHTML = ''; |
|
|
if (hasNumbers) { |
|
|
tagHTML += '<span class="segment-tag tag-error">أخطاء أرقام</span>'; |
|
|
} |
|
|
if (hasMissing) { |
|
|
tagHTML += '<span class="segment-tag tag-warning">نصوص مفقودة</span>'; |
|
|
} |
|
|
if (hasMeaning) { |
|
|
tagHTML += '<span class="segment-tag tag-info">اختلاف معنى</span>'; |
|
|
} |
|
|
if (!hasNumbers && !hasMissing && !hasMeaning) { |
|
|
tagHTML = '<span class="segment-tag tag-success">مطابق</span>'; |
|
|
} |
|
|
|
|
|
|
|
|
const segmentDiv = document.createElement('div'); |
|
|
segmentDiv.className = 'segment-comparison'; |
|
|
segmentDiv.dataset.segmentIndex = index; |
|
|
|
|
|
|
|
|
const sourceHighlighted = applyHighlights(segment.source, segment.target, segment.analysis); |
|
|
const targetHighlighted = applyHighlights(segment.target, segment.source, segment.analysis); |
|
|
|
|
|
|
|
|
segmentDiv.innerHTML = ` |
|
|
<div class="segment-header"> |
|
|
<div>المقطع ${index+1}</div> |
|
|
<div>${tagHTML}</div> |
|
|
</div> |
|
|
<div class="segment-content"> |
|
|
<div class="segment-source">${sourceHighlighted}</div> |
|
|
<div class="segment-target">${targetHighlighted}</div> |
|
|
</div> |
|
|
<div class="segment-notes"> |
|
|
${formatAnalysisText(segment.analysis)} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
container.appendChild(segmentDiv); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('segmentViewExplanation').innerHTML = generateSegmentViewExplanation(); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
document.querySelectorAll('.segment-source .sentence-with-error').forEach(element => { |
|
|
element.addEventListener('click', function() { |
|
|
|
|
|
const sentenceText = this.textContent; |
|
|
|
|
|
|
|
|
let errorType = 'general'; |
|
|
let errorSpecificText = ''; |
|
|
|
|
|
|
|
|
const numberHighlight = this.querySelector('.highlight-number'); |
|
|
const missingHighlight = this.querySelector('.highlight-missing'); |
|
|
const meaningHighlight = this.querySelector('.highlight-meaning'); |
|
|
|
|
|
if (numberHighlight) { |
|
|
errorType = 'number'; |
|
|
errorSpecificText = numberHighlight.textContent; |
|
|
} else if (missingHighlight) { |
|
|
errorType = 'missing'; |
|
|
errorSpecificText = missingHighlight.textContent; |
|
|
} else if (meaningHighlight) { |
|
|
errorType = 'meaning'; |
|
|
errorSpecificText = meaningHighlight.textContent; |
|
|
} |
|
|
|
|
|
const sentenceNumber = this.getAttribute('data-sentence-number'); |
|
|
|
|
|
|
|
|
let examples = null; |
|
|
|
|
|
if (errorType === 'number') { |
|
|
examples = { |
|
|
incorrect: `المادة <span class="highlight-number">٥</span> من القانون، تُطبق الغرامة المقدرة <span class="highlight-number">٤٥٠</span> دينارًا.`, |
|
|
correct: `المادة <span class="corrected-text">٥</span> من القانون، تُطبق الغرامة المقدرة <span class="corrected-text">٤٥٠</span> دينارًا.` |
|
|
}; |
|
|
} else if (errorType === 'missing') { |
|
|
examples = { |
|
|
incorrect: `اتفق الطرفان على أن يتم تسليم البضائع <span class="highlight-missing">خلال 30 يومًا من توقيع العقد</span>.`, |
|
|
correct: `اتفق الطرفان على أن يتم تسليم البضائع <span class="corrected-text">خلال 30 يومًا من توقيع العقد</span>.` |
|
|
}; |
|
|
} else if (errorType === 'meaning') { |
|
|
examples = { |
|
|
incorrect: `أقرت المحكمة <span class="highlight-meaning">بإدانة</span> المتهم.`, |
|
|
correct: `أقرت المحكمة <span class="corrected-text">ببراءة</span> المتهم.` |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
let explanation = ''; |
|
|
|
|
|
if (errorType === 'number') { |
|
|
explanation = `يبدو أن هناك اختلافًا في الأرقام في هذه الجملة. الرقم "${errorSpecificText}" في النص المصدر لا يتطابق مع النص الهدف. قد يكون هناك خطأ في نقل الرقم أو تحويله بين أنظمة الأرقام المختلفة (العربية، الإنجليزية، الهندية).`; |
|
|
} else if (errorType === 'missing') { |
|
|
explanation = `هناك نص مفقود في الترجمة! النص "${errorSpecificText}" موجود في المصدر ولكن لم يتم ترجمته أو تضمينه في النص الهدف. هذا يؤدي إلى فقدان جزء من المعنى الأصلي.`; |
|
|
} else if (errorType === 'meaning') { |
|
|
explanation = `هناك اختلاف مهم في المعنى! النص "${errorSpecificText}" تمت ترجمته بطريقة تغير المعنى الأصلي. قد يكون هناك خطأ في فهم السياق أو اختيار المصطلحات المناسبة.`; |
|
|
} else { |
|
|
explanation = `هناك خطأ في هذه الجملة: "${sentenceText}"<br><br>يرجى مراجعة الترجمة للتأكد من دقة المعنى والتطابق بين النصين.`; |
|
|
} |
|
|
|
|
|
|
|
|
showEnhancedPopup(errorType, sentenceText, explanation, examples); |
|
|
}); |
|
|
}); |
|
|
}, 100); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function setupInteractiveView() { |
|
|
|
|
|
allDifferences = []; |
|
|
let numberDiffCount = 0; |
|
|
let missingTextCount = 0; |
|
|
let meaningDiffCount = 0; |
|
|
|
|
|
|
|
|
analysisSegments.forEach((segment, segmentIndex) => { |
|
|
|
|
|
const numberMatches = Array.from(segment.analysis.matchAll(/<([^<>]+)>/g)); |
|
|
numberMatches.forEach(match => { |
|
|
allDifferences.push({ |
|
|
type: 'number', |
|
|
text: match[1], |
|
|
segmentIndex: segmentIndex, |
|
|
context: getContextAroundMatch(segment.source, match[1], 15) |
|
|
}); |
|
|
numberDiffCount++; |
|
|
}); |
|
|
|
|
|
|
|
|
const missingMatches = Array.from(segment.analysis.matchAll(/__(.*?)__/g)); |
|
|
missingMatches.forEach(match => { |
|
|
allDifferences.push({ |
|
|
type: 'missing', |
|
|
text: match[1], |
|
|
segmentIndex: segmentIndex, |
|
|
context: getContextAroundMatch(segment.source, match[1], 15) |
|
|
}); |
|
|
missingTextCount++; |
|
|
}); |
|
|
|
|
|
|
|
|
const meaningMatches = Array.from(segment.analysis.matchAll(/$$MEANING$$(.*?)$$\/MEANING$$/g)); |
|
|
meaningMatches.forEach(match => { |
|
|
allDifferences.push({ |
|
|
type: 'meaning', |
|
|
text: match[1], |
|
|
segmentIndex: segmentIndex, |
|
|
context: getContextAroundMatch(segment.source, match[1], 15) |
|
|
}); |
|
|
meaningDiffCount++; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('numberDiffCount').textContent = numberDiffCount; |
|
|
document.getElementById('missingTextCount').textContent = missingTextCount; |
|
|
document.getElementById('meaningDiffCount').textContent = meaningDiffCount; |
|
|
|
|
|
|
|
|
const recommendationsContainer = document.getElementById('diffRecommendations'); |
|
|
recommendationsContainer.innerHTML = ''; |
|
|
|
|
|
if (allDifferences.length === 0) { |
|
|
recommendationsContainer.innerHTML = ` |
|
|
<div class="flex items-center justify-center p-4"> |
|
|
<div class="text-center"> |
|
|
<i class="fas fa-check-circle text-green-500 text-4xl mb-2"></i> |
|
|
<p>لا توجد اختلافات تحتاج إلى معالجة!</p> |
|
|
</div> |
|
|
</div>`; |
|
|
} else { |
|
|
|
|
|
if (numberDiffCount > 0) { |
|
|
recommendationsContainer.innerHTML += `<p class="flex items-center"><i class="fas fa-hashtag text-yellow-500 ml-2"></i> راجع الأرقام واحرص على تطابقها بين النصين.</p>`; |
|
|
} |
|
|
if (missingTextCount > 0) { |
|
|
recommendationsContainer.innerHTML += `<p class="flex items-center"><i class="fas fa-plus-circle text-blue-500 ml-2"></i> أضف النصوص المفقودة في الترجمة لضمان اكتمال المحتوى.</p>`; |
|
|
} |
|
|
if (meaningDiffCount > 0) { |
|
|
recommendationsContainer.innerHTML += `<p class="flex items-center"><i class="fas fa-exchange-alt text-red-500 ml-2"></i> صحح اختلافات المعنى لضمان دقة الترجمة.</p>`; |
|
|
} |
|
|
|
|
|
|
|
|
recommendationsContainer.innerHTML += `<p class="flex items-center mt-2 pt-2 border-t border-green-200"><i class="fas fa-th-large text-green-500 ml-2"></i> استخدم وضع العرض المقسم للتعديل الدقيق.</p>`; |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('prevDiff').addEventListener('click', showPreviousDifference); |
|
|
document.getElementById('nextDiff').addEventListener('click', showNextDifference); |
|
|
|
|
|
|
|
|
if (allDifferences.length > 0) { |
|
|
currentDiffIndex = 0; |
|
|
document.getElementById('prevDiff').disabled = false; |
|
|
document.getElementById('nextDiff').disabled = false; |
|
|
displayCurrentDifference(); |
|
|
} else { |
|
|
document.getElementById('diffCounter').textContent = "0/0"; |
|
|
document.getElementById('currentDiffDisplay').innerHTML = ` |
|
|
<div class="flex flex-col items-center justify-center py-8"> |
|
|
<div class="text-6xl text-green-300 mb-4"> |
|
|
<i class="fas fa-check-circle"></i> |
|
|
</div> |
|
|
<p class="text-gray-500 text-center">لم يتم العثور على اختلافات</p> |
|
|
<p class="text-green-500 text-center mt-2 font-bold">النصوص متطابقة!</p> |
|
|
</div>`; |
|
|
document.getElementById('prevDiff').disabled = true; |
|
|
document.getElementById('nextDiff').disabled = true; |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('interactiveViewExplanation').innerHTML = generateInteractiveViewExplanation(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getContextAroundMatch(text, match, contextSize) { |
|
|
const index = text.indexOf(match); |
|
|
if (index === -1) return ""; |
|
|
|
|
|
|
|
|
const start = Math.max(0, text.lastIndexOf('.', index) + 1); |
|
|
const end = Math.min(text.length, text.indexOf('.', index + match.length) + 1); |
|
|
|
|
|
|
|
|
let context; |
|
|
if (end - start < 10) { |
|
|
const altStart = Math.max(0, index - contextSize); |
|
|
const altEnd = Math.min(text.length, index + match.length + contextSize); |
|
|
context = text.substring(altStart, altEnd); |
|
|
} else { |
|
|
context = text.substring(start, end); |
|
|
} |
|
|
|
|
|
|
|
|
const highlightedContext = context.replace( |
|
|
new RegExp(`(${escapeRegExp(match)})`, 'g'), |
|
|
`<span class="font-bold text-blue-700 bg-blue-100 px-1 rounded">\$1</span>` |
|
|
); |
|
|
|
|
|
return highlightedContext; |
|
|
} |
|
|
|
|
|
|
|
|
function showPreviousDifference() { |
|
|
if (allDifferences.length === 0) return; |
|
|
|
|
|
currentDiffIndex = (currentDiffIndex - 1 + allDifferences.length) % allDifferences.length; |
|
|
displayCurrentDifference(); |
|
|
} |
|
|
|
|
|
|
|
|
function showNextDifference() { |
|
|
if (allDifferences.length === 0) return; |
|
|
|
|
|
currentDiffIndex = (currentDiffIndex + 1) % allDifferences.length; |
|
|
displayCurrentDifference(); |
|
|
} |
|
|
|
|
|
|
|
|
function displayCurrentDifference() { |
|
|
if (allDifferences.length === 0 || currentDiffIndex < 0) { |
|
|
document.getElementById('currentDiffDisplay').innerHTML = |
|
|
'<p class="text-gray-500 text-center">اضغط "التالي" للبدء في استعراض الاختلافات</p>'; |
|
|
document.getElementById('diffCounter').textContent = "0/0"; |
|
|
document.getElementById('diffDetailedView').classList.add('hidden'); |
|
|
document.getElementById('diffReference').classList.add('hidden'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const diff = allDifferences[currentDiffIndex]; |
|
|
const segment = analysisSegments[diff.segmentIndex]; |
|
|
|
|
|
let typeLabel = ''; |
|
|
let typeClass = ''; |
|
|
let icon = ''; |
|
|
|
|
|
if (diff.type === 'number') { |
|
|
typeLabel = 'اختلاف رقمي'; |
|
|
typeClass = 'bg-yellow-100 text-yellow-800'; |
|
|
icon = 'fas fa-hashtag'; |
|
|
} else if (diff.type === 'missing') { |
|
|
typeLabel = 'نص مفقود'; |
|
|
typeClass = 'bg-blue-100 text-blue-800'; |
|
|
icon = 'fas fa-minus-circle'; |
|
|
} else if (diff.type === 'meaning') { |
|
|
typeLabel = 'اختلاف معنى'; |
|
|
typeClass = 'bg-red-100 text-red-800'; |
|
|
icon = 'fas fa-exclamation-circle'; |
|
|
} |
|
|
|
|
|
let highlightedContext = diff.context; |
|
|
|
|
|
|
|
|
document.getElementById('currentDiffDisplay').innerHTML = ` |
|
|
<div class="mb-3 flex items-center justify-between"> |
|
|
<div class="flex items-center"> |
|
|
<span class="rounded-full px-3 py-1 text-sm ${typeClass} flex items-center"> |
|
|
<i class="${icon} ml-1"></i> |
|
|
${typeLabel} |
|
|
</span> |
|
|
<span class="mr-2 text-gray-500">المقطع ${diff.segmentIndex + 1}</span> |
|
|
</div> |
|
|
<span class="text-sm text-gray-500">${currentDiffIndex + 1}/${allDifferences.length}</span> |
|
|
</div> |
|
|
<div class="mb-3 font-bold flex items-center"> |
|
|
<i class="fas fa-quote-right text-gray-400 ml-2"></i> |
|
|
<span>السياق:</span> |
|
|
</div> |
|
|
<div class="bg-gray-50 p-4 rounded-lg mb-3 leading-relaxed">${highlightedContext}</div> |
|
|
<div class="text-sm text-gray-600"> |
|
|
<div class="font-bold mb-1 flex items-center"> |
|
|
<i class="fas fa-lightbulb text-yellow-500 ml-2"></i> |
|
|
<span>التوصية:</span> |
|
|
</div> |
|
|
<div class="p-2 bg-yellow-50 rounded-lg border-r-3 border-yellow-400"> |
|
|
${getRecommendationForDiff(diff)} |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
document.getElementById('diffCounter').textContent = `${currentDiffIndex + 1}/${allDifferences.length}`; |
|
|
|
|
|
|
|
|
document.getElementById('diffDetailedView').classList.remove('hidden'); |
|
|
|
|
|
|
|
|
document.getElementById('diffSourceText').innerHTML = ` |
|
|
<div class="p-2 bg-white rounded-lg border border-gray-200 leading-relaxed"> |
|
|
${highlightSourceText(segment.source, diff.text)} |
|
|
</div>`; |
|
|
|
|
|
|
|
|
document.getElementById('diffTargetText').innerHTML = ` |
|
|
<div class="p-2 bg-white rounded-lg border border-gray-200 leading-relaxed"> |
|
|
${highlightTargetText(segment.target, diff)} |
|
|
</div>`; |
|
|
|
|
|
|
|
|
if (diff.reference) { |
|
|
document.getElementById('diffReference').classList.remove('hidden'); |
|
|
document.getElementById('diffReferenceText').textContent = diff.reference; |
|
|
} else { |
|
|
document.getElementById('diffReference').classList.add('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function highlightSourceText(sourceText, diffText) { |
|
|
return sourceText.replace( |
|
|
new RegExp(`(${escapeRegExp(diffText)})`, 'g'), |
|
|
`<mark class="bg-yellow-200 px-1 rounded">\$1</mark>` |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
function highlightTargetText(targetText, diff) { |
|
|
if (diff.type === 'missing') { |
|
|
|
|
|
const words = diff.text.split(/\s+/).filter(w => w.length > 3); |
|
|
let markedText = targetText; |
|
|
|
|
|
|
|
|
const contextBefore = diff.context.split(diff.text)[0]?.trim(); |
|
|
const contextAfter = diff.context.split(diff.text)[1]?.trim(); |
|
|
|
|
|
if (contextBefore && targetText.includes(contextBefore)) { |
|
|
const index = targetText.indexOf(contextBefore) + contextBefore.length; |
|
|
markedText = |
|
|
targetText.substring(0, index) + |
|
|
` <mark class="bg-blue-200 px-1 rounded border border-blue-400 border-dashed">[يجب إضافة: ${diff.text}]</mark> ` + |
|
|
targetText.substring(index); |
|
|
} else if (contextAfter && targetText.includes(contextAfter)) { |
|
|
const index = targetText.indexOf(contextAfter); |
|
|
markedText = |
|
|
targetText.substring(0, index) + |
|
|
` <mark class="bg-blue-200 px-1 rounded border border-blue-400 border-dashed">[يجب إضافة: ${diff.text}]</mark> ` + |
|
|
targetText.substring(index); |
|
|
} else { |
|
|
|
|
|
markedText += ` <mark class="bg-blue-200 px-1 rounded border border-blue-400 border-dashed">[نص مفقود: ${diff.text}]</mark>`; |
|
|
} |
|
|
|
|
|
return markedText; |
|
|
} |
|
|
else if (diff.type === 'number') { |
|
|
|
|
|
const numbers = targetText.match(/\d+/g) || []; |
|
|
if (numbers.length === 0) return targetText; |
|
|
|
|
|
let markedText = targetText; |
|
|
|
|
|
|
|
|
for (const num of numbers) { |
|
|
if (num !== diff.text) { |
|
|
markedText = markedText.replace( |
|
|
new RegExp(`(${num})`, 'g'), |
|
|
`<mark class="bg-yellow-200 px-1 rounded">${num}</mark>` |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
return markedText; |
|
|
} |
|
|
else { |
|
|
|
|
|
const words = diff.text.split(/\s+/).filter(w => w.length > 3); |
|
|
let markedText = targetText; |
|
|
|
|
|
for (const word of words) { |
|
|
if (targetText.includes(word)) { |
|
|
const regex = new RegExp(`(.{0,10}${escapeRegExp(word)}.{0,10})`, 'g'); |
|
|
markedText = markedText.replace( |
|
|
regex, |
|
|
`<mark class="bg-red-200 px-1 rounded">\$1</mark>` |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
return markedText; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function getRecommendationForDiff(diff) { |
|
|
if (diff.type === 'number') { |
|
|
return 'يا صديقي، يبدو أن هناك خطأ في الرقم. في النص المصدر يظهر الرقم بشكل مختلف عما هو في النص الهدف. تأكد من تطابق الأرقام في النصين واستخدام نفس الصيغة.'; |
|
|
} else if (diff.type === 'missing') { |
|
|
return 'يا صديقي، هناك نص مفقود في الترجمة! تأكد من إضافة هذا النص لأنه موجود في المصدر ويحتوي على معلومات مهمة للمحافظة على اكتمال المعنى.'; |
|
|
} else if (diff.type === 'meaning') { |
|
|
return 'يا صديقي، هناك اختلاف في المعنى بين النصين. النص في المصدر يقول شيئًا مختلفًا عما تمت ترجمته. راجع هذا الجزء للتأكد من دقة الترجمة ونقل المعنى الصحيح.'; |
|
|
} |
|
|
return ''; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function escapeRegExp(string) { |
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function formatAnalysisText(text) { |
|
|
|
|
|
text = text.replace(/الأرقام/g, '<span class="font-bold text-blue-600">الأرقام</span>'); |
|
|
text = text.replace(/المفقودة/g, '<span class="font-bold text-blue-600">المفقودة</span>'); |
|
|
text = text.replace(/المعنى/g, '<span class="font-bold text-blue-600">المعنى</span>'); |
|
|
|
|
|
|
|
|
text = text.replace(/<([^<>]+)>/g, '<span class="highlight-number"><\$1></span>'); |
|
|
text = text.replace(/__(.*?)__/g, '<span class="highlight-missing">__\$1__</span>'); |
|
|
text = text.replace(/$$MEANING$$(.*?)$$\/MEANING$$/g, '<span class="highlight-meaning">\$1</span>'); |
|
|
|
|
|
return text; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function generateSegmentViewExplanation() { |
|
|
return ` |
|
|
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200"> |
|
|
<h4 class="font-bold text-blue-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-info-circle ml-2"></i> |
|
|
عن العرض المقسم |
|
|
</h4> |
|
|
<p class="text-blue-800"> |
|
|
يقوم هذا العرض بتقسيم النص إلى مقاطع متوازية لتسهيل المقارنة بين النص المصدر والترجمة. |
|
|
انقر على النص المميز للحصول على تفاصيل إضافية حول الاختلاف. |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4 p-4 bg-yellow-50 rounded-lg border border-yellow-200"> |
|
|
<h4 class="font-bold text-yellow-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-lightbulb ml-2"></i> |
|
|
نصائح للاستخدام |
|
|
</h4> |
|
|
<ul class="list-disc pr-8 text-sm text-yellow-700 space-y-1"> |
|
|
<li>استخدم فلتر الأخطاء في الأعلى لعرض نوع محدد من الاختلافات</li> |
|
|
<li>انقر على النص المميز للحصول على تفاصيل إضافية</li> |
|
|
<li>قارن المقاطع بشكل مباشر لتحديد المشكلات بدقة</li> |
|
|
</ul> |
|
|
</div>`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function generateInteractiveViewExplanation() { |
|
|
return ` |
|
|
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200"> |
|
|
<h4 class="font-bold text-blue-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-info-circle ml-2"></i> |
|
|
عن العرض التفاعلي |
|
|
</h4> |
|
|
<p class="text-blue-800"> |
|
|
يقوم هذا العرض بتمكينك من استعراض الاختلافات واحدة تلو الأخرى بطريقة تفاعلية. |
|
|
استخدم أزرار "التالي" و"السابق" للتنقل بين الاختلافات المختلفة. |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4 p-4 bg-yellow-50 rounded-lg border border-yellow-200"> |
|
|
<h4 class="font-bold text-yellow-800 mb-2 flex items-center"> |
|
|
<i class="fas fa-lightbulb ml-2"></i> |
|
|
مزايا هذا العرض |
|
|
</h4> |
|
|
<ul class="list-disc pr-8 text-sm text-yellow-700 space-y-1"> |
|
|
<li>تركيز أفضل على كل اختلاف على حدة</li> |
|
|
<li>رؤية النص المصدر والهدف معًا لكل اختلاف</li> |
|
|
<li>الحصول على توصيات محددة لمعالجة كل مشكلة</li> |
|
|
</ul> |
|
|
</div>`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isPartiallyIncluded(text, targetText) { |
|
|
|
|
|
const keywords = text.split(/\s+/).filter(word => word.length > 3); |
|
|
let foundKeywords = 0; |
|
|
|
|
|
|
|
|
for (const keyword of keywords) { |
|
|
if (targetText.includes(keyword)) { |
|
|
foundKeywords++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return keywords.length > 0 && foundKeywords / keywords.length > 0.3; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function downloadWordReport() { |
|
|
|
|
|
let wordContent = ` |
|
|
<!DOCTYPE html> |
|
|
<html xmlns:o='urn:schemas-microsoft-com:office:office' |
|
|
xmlns:w='urn:schemas-microsoft-com:office:word' |
|
|
xmlns='http://www.w3.org/TR/REC-html40'> |
|
|
<head> |
|
|
<meta charset='utf-8'> |
|
|
<title>تقرير تحليل النصوص - شركة موندو لينجوا</title> |
|
|
<style> |
|
|
@page { |
|
|
size: 21cm 29.7cm; |
|
|
margin: 2cm; |
|
|
mso-page-orientation: portrait; |
|
|
} |
|
|
body { |
|
|
font-family: 'Arial', sans-serif; |
|
|
direction: rtl; |
|
|
text-align: right; |
|
|
} |
|
|
h1, h2, h3, h4 { |
|
|
color: #3b82f6; |
|
|
} |
|
|
.header { |
|
|
text-align: center; |
|
|
margin-bottom: 20px; |
|
|
border-bottom: 1px solid #ddd; |
|
|
padding-bottom: 10px; |
|
|
} |
|
|
.header h1 { |
|
|
color: #3b82f6; |
|
|
margin-bottom: 5px; |
|
|
} |
|
|
.segment { |
|
|
margin-bottom: 30px; |
|
|
page-break-inside: avoid; |
|
|
} |
|
|
.segment-title { |
|
|
background-color: #f3f4f6; |
|
|
padding: 5px; |
|
|
margin-bottom: 10px; |
|
|
font-weight: bold; |
|
|
border-right: 4px solid #3b82f6; |
|
|
} |
|
|
.source-text, .target-text { |
|
|
margin-bottom: 10px; |
|
|
padding: 10px; |
|
|
border: 1px solid #e5e7eb; |
|
|
} |
|
|
.source-text { |
|
|
background-color: #f0f9ff; |
|
|
} |
|
|
.target-text { |
|
|
background-color: #fdf2f8; |
|
|
} |
|
|
.analysis { |
|
|
margin-top: 10px; |
|
|
background-color: #fffbeb; |
|
|
padding: 10px; |
|
|
border: 1px solid #fcd34d; |
|
|
} |
|
|
.error-numbers { |
|
|
color: #b45309; |
|
|
} |
|
|
.error-missing { |
|
|
color: #1e40af; |
|
|
} |
|
|
.error-meaning { |
|
|
color: #b91c1c; |
|
|
} |
|
|
.footer { |
|
|
text-align: center; |
|
|
margin-top: 20px; |
|
|
font-size: 10px; |
|
|
color: #6b7280; |
|
|
border-top: 1px solid #ddd; |
|
|
padding-top: 10px; |
|
|
} |
|
|
.summary { |
|
|
margin: 20px 0; |
|
|
border: 1px solid #e5e7eb; |
|
|
padding: 10px; |
|
|
} |
|
|
.summary-title { |
|
|
font-weight: bold; |
|
|
margin-bottom: 10px; |
|
|
border-bottom: 1px solid #e5e7eb; |
|
|
padding-bottom: 5px; |
|
|
} |
|
|
table { |
|
|
width: 100%; |
|
|
border-collapse: collapse; |
|
|
margin: 10px 0; |
|
|
} |
|
|
table, th, td { |
|
|
border: 1px solid #e5e7eb; |
|
|
padding: 5px; |
|
|
} |
|
|
th { |
|
|
background-color: #f3f4f6; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="header"> |
|
|
<h1>تقرير تحليل النصوص المترجمة</h1> |
|
|
<div>شركة موندو لينجوا للترجمة المعتمدة</div> |
|
|
<div>تاريخ التقرير: ${new Date().toLocaleDateString('ar-EG')}</div> |
|
|
${correctionApplied ? '<div style="color: #22c55e; font-weight: bold;">تم تطبيق تصحيحات المستندات الرسمية</div>' : ''} |
|
|
</div> |
|
|
|
|
|
<div class="summary"> |
|
|
<div class="summary-title">ملخص التحليل</div> |
|
|
<table> |
|
|
<tr> |
|
|
<th>نوع الاختلاف</th> |
|
|
<th>العدد</th> |
|
|
</tr>`; |
|
|
|
|
|
|
|
|
let totalNumbers = 0; |
|
|
let totalMissing = 0; |
|
|
let totalMeaning = 0; |
|
|
|
|
|
analysisSegments.forEach(segment => { |
|
|
totalNumbers += segment.errors.numbers; |
|
|
totalMissing += segment.errors.missing; |
|
|
totalMeaning += segment.errors.meaning; |
|
|
}); |
|
|
|
|
|
wordContent += ` |
|
|
<tr> |
|
|
<td>اختلافات الأرقام</td> |
|
|
<td>${totalNumbers}</td> |
|
|
</tr> |
|
|
<tr> |
|
|
<td>النصوص المفقودة</td> |
|
|
<td>${totalMissing}</td> |
|
|
</tr> |
|
|
<tr> |
|
|
<td>اختلافات المعنى</td> |
|
|
<td>${totalMeaning}</td> |
|
|
</tr> |
|
|
<tr> |
|
|
<td><strong>إجمالي الاختلافات</strong></td> |
|
|
<td><strong>${totalNumbers + totalMissing + totalMeaning}</strong></td> |
|
|
</tr> |
|
|
</table> |
|
|
</div>`; |
|
|
|
|
|
|
|
|
analysisSegments.forEach((segment, index) => { |
|
|
wordContent += ` |
|
|
<div class="segment"> |
|
|
<div class="segment-title">القسم ${index + 1}</div> |
|
|
<div class="source-text"> |
|
|
<strong>النص المصدر:</strong><br> |
|
|
${segment.source} |
|
|
</div> |
|
|
<div class="target-text"> |
|
|
<strong>النص الهدف:</strong><br> |
|
|
${segment.target} |
|
|
</div> |
|
|
<div class="analysis"> |
|
|
<strong>التحليل:</strong><br> |
|
|
${segment.analysis.replace(/<([^<>]+)>/g, '<span class="error-numbers"><$1></span>') |
|
|
.replace(/__(.*?)__/g, '<span class="error-missing">__$1__</span>') |
|
|
.replace(/\[MEANING\](.*?)\[\/MEANING\]/g, '<span class="error-meaning">$1</span>')} |
|
|
</div> |
|
|
</div>`; |
|
|
}); |
|
|
|
|
|
|
|
|
wordContent += ` |
|
|
<div class="footer"> |
|
|
تم إنشاء هذا التقرير تلقائيًا بواسطة نظام المراجع الذكي - جميع الحقوق محفوظة لشركة فاست برو للبرمجيات والذكاء الاصطناعي © ${new Date().getFullYear()} |
|
|
</div> |
|
|
</body> |
|
|
</html>`; |
|
|
|
|
|
|
|
|
const blob = new Blob(['\ufeff', wordContent], { type: 'application/msword' }); |
|
|
const url = URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = 'تقرير_تحليل_النصوص.doc'; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
URL.revokeObjectURL(url); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addError(message, type = 'error') { |
|
|
const errorsList = document.getElementById('errorsList'); |
|
|
if (!errorsList) return; |
|
|
const errorDiv = document.createElement('div'); |
|
|
|
|
|
|
|
|
let bgColor, borderColor, textColor, icon; |
|
|
|
|
|
if (type === 'error') { |
|
|
bgColor = 'bg-red-50'; |
|
|
borderColor = 'border-red-200'; |
|
|
textColor = 'text-red-700'; |
|
|
icon = 'exclamation-circle text-red-500'; |
|
|
} else if (type === 'info') { |
|
|
bgColor = 'bg-blue-50'; |
|
|
borderColor = 'border-blue-200'; |
|
|
textColor = 'text-blue-700'; |
|
|
icon = 'info-circle text-blue-500'; |
|
|
} else { |
|
|
bgColor = 'bg-yellow-50'; |
|
|
borderColor = 'border-yellow-200'; |
|
|
textColor = 'text-yellow-700'; |
|
|
icon = 'exclamation-triangle text-yellow-500'; |
|
|
} |
|
|
|
|
|
errorDiv.className = `p-4 rounded-xl ${bgColor} ${textColor} border ${borderColor} shadow-sm mb-3 animate-scale`; |
|
|
errorDiv.innerHTML = ` |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-${icon} text-xl ml-3"></i> |
|
|
<span class="font-medium">${message}</span> |
|
|
<button class="mr-auto text-gray-400 hover:text-gray-600 transition-colors"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div>`; |
|
|
|
|
|
|
|
|
const closeBtn = errorDiv.querySelector('button'); |
|
|
closeBtn.addEventListener('click', () => { |
|
|
errorDiv.style.opacity = '0'; |
|
|
errorDiv.style.transform = 'translateY(-10px)'; |
|
|
errorDiv.style.transition = 'opacity 0.3s, transform 0.3s'; |
|
|
setTimeout(() => { |
|
|
errorsList.removeChild(errorDiv); |
|
|
}, 300); |
|
|
}); |
|
|
|
|
|
errorsList.appendChild(errorDiv); |
|
|
|
|
|
|
|
|
errorDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyErrorFilter(filterType) { |
|
|
|
|
|
if (filterType === 'all') { |
|
|
|
|
|
document.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => { |
|
|
el.style.display = ''; |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.sentence-with-error').forEach(el => { |
|
|
el.classList.remove('opacity-50'); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
document.querySelectorAll('.highlight-number, .highlight-missing, .highlight-meaning, .completely-missing, .partially-missing').forEach(el => { |
|
|
el.style.display = 'none'; |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.sentence-with-error').forEach(el => { |
|
|
el.classList.add('opacity-50'); |
|
|
}); |
|
|
|
|
|
|
|
|
let selector = ''; |
|
|
if (filterType === 'number') selector = '.highlight-number'; |
|
|
else if (filterType === 'missing') selector = '.highlight-missing, .completely-missing, .partially-missing'; |
|
|
else if (filterType === 'meaning') selector = '.highlight-meaning'; |
|
|
|
|
|
document.querySelectorAll(selector).forEach(el => { |
|
|
el.style.display = ''; |
|
|
|
|
|
const errorId = el.getAttribute('data-error-id'); |
|
|
if (errorId) { |
|
|
document.querySelectorAll(`.sentence-with-error[data-error-id="${errorId}"]`).forEach(sentence => { |
|
|
sentence.classList.remove('opacity-50'); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |