pcb-defect-detector-project/new-version
0
1// ========== Global Variables ==========2let currentFile = null;3let currentImagePreview = null;4let currentDetections = [];5let currentParameters = {};6let currentAnnotatedImage = null;7 8// ========== Auth Variables ==========9let isLoginMode = true;10let currentUser = null;11let currentBoards = [];12 13// ========== DOM Elements ==========14const fileInput = document.getElementById('fileInput');15const uploadBtn = document.getElementById('uploadBtn');16const defectBtn = document.getElementById('defectBtn');17const uploadArea = document.getElementById('uploadArea');18const previewImg = document.getElementById('previewImg');19const loadingDiv = document.getElementById('loading');20const resultsSection = document.getElementById('resultsSection');21const defectsListDiv = document.getElementById('defectsList');22const summaryContentDiv = document.getElementById('summaryContent');23const annotatedImage = document.getElementById('annotatedImage');24const statusReportDiv = document.getElementById('statusReport');25 26 27// API Configuration28const API_BASE_URL = 'https://pcb-defect-detector-project-new-version.hf.space';29const DETECT_ENDPOINT = `${API_BASE_URL}/detect`;30const LOGIN_ENDPOINT = `${API_BASE_URL}/api/login`;31const REGISTER_ENDPOINT = `${API_BASE_URL}/api/register`;32const BOARDS_ENDPOINT = `${API_BASE_URL}/api/boards`;33 34// ========== Severity & Recommendations ==========35const SEVERITY_MAP = {36 "missing_hole": { score: 7.5, level: "high", icon: "🟡", color: "#f59e0b" },37 "mouse_bite": { score: 4.0, level: "low", icon: "🟢", color: "#10b981" },38 "open_circuit": { score: 8.5, level: "critical", icon: "🟠", color: "#f97316" },39 "short_circuit": { score: 9.0, level: "critical", icon: "🔴", color: "#ef4444" },40 "spur": { score: 6.0, level: "medium", icon: "🟣", color: "#8b5cf6" },41 "spurious_copper": { score: 5.0, level: "medium", icon: "🩷", color: "#ec4899" }42};43 44const LOCAL_RECOMMENDATIONS = {45 "missing_hole": { description_ar: "يوجد ثقب مفقود في اللوحة حيث يجب تركيب أحد المكونات.", recommendation_ar: "إعادة حفر اللوحة أو إصلاحها يدوياً باستخدام مثقاب دقيق.", priority: "high" },46 "mouse_bite": { description_ar: "حواف اللوحة تبدو غير منتظمة بسبب مشاكل في التصنيع.", recommendation_ar: "تنعيم الحواف باستخدام ورق صنفرة ناعم أو إعادة التصنيع.", priority: "medium" },47 "open_circuit": { description_ar: "المسار الكهربائي مقطوع، لا تصل الإشارة.", recommendation_ar: "توصيل المسار بسلك رفيع أو إصلاحه يدوياً باستخدام القصدير.", priority: "high" },48 "short_circuit": { description_ar: "اتصال غير صحيح بين مسارين كهربائيين.", recommendation_ar: "إزالة الاتصال الزائد فوراً باستخدام أداة حادة. خطر كبير!", priority: "critical" },49 "spur": { description_ar: "نتوء صغير غير طبيعي في المسار الكهربائي.", recommendation_ar: "إزالة النتوء يدوياً باستخدام مشرط دقيق.", priority: "medium" },50 "spurious_copper": { description_ar: "وجود نحاس إضافي في مكان غير صحيح على اللوحة.", recommendation_ar: "تنظيف وإزالة النحاس الزائد باستخدام أداة كشط.", priority: "medium" }51};52 53// ========== Helper Functions ==========54function showToast(message, type = "info") {55 const toast = document.createElement('div');56 toast.className = `toast-notification ${type}`;57 toast.innerHTML = `<i class="fas ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i><span>${message}</span>`;58 document.body.appendChild(toast);59 setTimeout(() => { toast.style.animation = "slideOutLeft 0.3s ease"; setTimeout(() => toast.remove(), 300); }, 3000);60}61 62function getConfidenceLevel(confidence) {63 const percent = confidence * 100;64 if (percent < 50) return { level: 'ضعيف', class: 'confidence-low' };65 if (percent < 75) return { level: 'متوسط', class: 'confidence-medium' };66 return { level: 'أكيد', class: 'confidence-high' };67}68 69function getSeverityInfo(defectType) { return SEVERITY_MAP[defectType] || { score: 5.0, level: "medium", icon: "⚠️", color: "#gray" }; }70function getLocalRecommendation(defectType) { return LOCAL_RECOMMENDATIONS[defectType] || { description_ar: "عطل تم اكتشافه في اللوحة الإلكترونية.", recommendation_ar: "يفضل فحص اللوحة يدوياً أو استشارة فني مختص.", priority: "medium" }; }71function getDefectTypeArabic(defectType) { const map = { "missing_hole": "ثقب مفقود", "mouse_bite": "عضّة فأر", "open_circuit": "دارة مفتوحة", "short_circuit": "دارة قصيرة", "spur": "نتوء نحاسي", "spurious_copper": "نحاس زائد" }; return map[defectType] || defectType; }72 73const DEFECT_COLORS = {74 "missing_hole": { stroke: "#f59e0b", fill: "#f59e0b" },75 "mouse_bite": { stroke: "#10b981", fill: "#10b981" },76 "open_circuit": { stroke: "#f97316", fill: "#f97316" },77 "short_circuit": { stroke: "#ef4444", fill: "#ef4444" },78 "spur": { stroke: "#8b5cf6", fill: "#8b5cf6" },79 "spurious_copper": { stroke: "#ec4899", fill: "#ec4899" }80};81function getDefectColor(defectType) { return DEFECT_COLORS[defectType] || { stroke: "#00ff00", fill: "#00ff00" }; }82 83function calculateBoardDecision(detections) {84 const criticalCount = detections.filter(d => getSeverityInfo(d.defect_type).level === "critical").length;85 const highCount = detections.filter(d => getSeverityInfo(d.defect_type).level === "high").length;86 if (criticalCount > 0) return { decision: "❌ مرفوض - يحتاج إعادة تصنيع", class: "decision-fail", reason: `تم اكتشاف ${criticalCount} عيب/عيوب حرجة تؤثر على وظيفة اللوحة بشكل كامل.` };87 if (highCount > 0) return { decision: "⚠️ مقبول مشروط - يحتاج إصلاح", class: "decision-conditional", reason: `تم اكتشاف ${highCount} عيب/عيوب عالية الخطورة. يمكن إصلاحها قبل الاستخدام.` };88 if (detections.length > 3) return { decision: "🟡 مقبول مع مراجعة", class: "decision-conditional", reason: `تم اكتشاف ${detections.length} عيب ولكنها غير حرجة. يوصى بمراجعة الجودة.` };89 return { decision: "✅ مقبول - اللوحة سليمة", class: "decision-pass", reason: "اللوحة تستوفي معايير الجودة المطلوبة ولا تحتوي على عيوب حرجة." };90}91 92function generateSmartRecommendations(detections) {93 if (!detections || detections.length === 0) return { analysis: "✅ لم يتم اكتشاف أي عيوب في لوحة الدائرة المطبوعة. اللوحة سليمة.", repair_suggestions: [], preventive_actions: [{ title: "الصيانة الدورية", description: "استمر في إجراء فحوصات دورية للوحات لضمان الجودة المستمرة.", priority: "low" }], overall_risk_assessment: "منخفض - لا توجد عيوب مكتشفة", estimated_repair_time: "0 دقيقة" };94 const criticalCount = detections.filter(d => getSeverityInfo(d.defect_type).level === "critical").length;95 const highCount = detections.filter(d => getSeverityInfo(d.defect_type).level === "high").length;96 let risk = "متوسط - يمكن جدولة الإصلاح", estimatedTime = "5-15 دقيقة";97 if (criticalCount > 0) { risk = "حرج - يتطلب تدخل فوري"; estimatedTime = "30-60 دقيقة"; }98 else if (highCount > 0) { risk = "مرتفع - يجب المعالجة في أقرب وقت"; estimatedTime = "15-30 دقيقة"; }99 const defectNames = detections.slice(0, 3).map(d => d.defect_type_ar || getDefectTypeArabic(d.defect_type)).join(", ");100 let analysis = `تم اكتشاف ${detections.length} عيب/عيوب في اللوحة، أبرزها: ${defectNames}. `;101 if (criticalCount > 0) analysis += "توجد عيوب حرجة (دارة قصيرة/مفتوحة) قد تؤدي إلى فشل كامل للوحة. ";102 else if (highCount > 0) analysis += "توجد عيوب عالية الخطورة تؤثر على أداء الدائرة. ";103 else analysis += "العيوب المكتشفة متوسطة إلى منخفضة الخطورة.";104 const repairSuggestions = [];105 for (const d of detections.slice(0, 3)) { const rec = getLocalRecommendation(d.defect_type); if (!repairSuggestions.some(s => s.title === rec.recommendation_ar.substring(0, 30))) repairSuggestions.push({ title: `إصلاح ${d.defect_type_ar}`, description: rec.recommendation_ar, priority: rec.priority }); }106 return { analysis, repair_suggestions: repairSuggestions, preventive_actions: [{ title: "تحسين عملية التصنيع", description: "قم بمراجعة إعدادات آلة الحفر والطحن للتأكد من دقتها.", priority: "medium" }, { title: "فحص المواد الخام", description: "تأكد من جودة رقائق النحاس المستخدمة في التصنيع.", priority: "medium" }, { title: "التدريب المستمر", description: "درّب فريق التصنيع على التعرف على العيوب الشائعة وتجنبها.", priority: "low" }], overall_risk_assessment: risk, estimated_repair_time: estimatedTime };107}108 109// ========== Draw Functions ==========110function drawBoundingBoxes(imageSrc, detections) {111 return new Promise((resolve) => {112 const img = new Image();113 img.onload = () => {114 const canvas = document.createElement('canvas');115 const ctx = canvas.getContext('2d');116 canvas.width = img.width;117 canvas.height = img.height;118 ctx.drawImage(img, 0, 0);119 const sortedDetections = [...detections].sort((a, b) => b.confidence - a.confidence);120 sortedDetections.forEach(det => {121 const [x1, y1, x2, y2] = det.bbox;122 const width = x2 - x1, height = y2 - y1;123 const color = getDefectColor(det.defect_type);124 const defectNameAr = getDefectTypeArabic(det.defect_type);125 const confidencePercent = (det.confidence * 100).toFixed(1);126 const text = `${defectNameAr} (${confidencePercent}%)`;127 ctx.strokeStyle = color.stroke;128 ctx.lineWidth = 3;129 ctx.strokeRect(x1, y1, width, height);130 ctx.font = 'bold 14px Cairo';131 const textWidth = ctx.measureText(text).width;132 const textHeight = 20;133 ctx.fillStyle = color.fill;134 ctx.fillRect(x1, y1 - textHeight - 2, textWidth + 10, textHeight + 4);135 ctx.fillStyle = '#ffffff';136 ctx.fillText(text, x1 + 5, y1 - 5);137 });138 resolve(canvas.toDataURL('image/jpeg'));139 };140 img.src = imageSrc;141 });142}143 144// ========== Display Results ==========145function displayResultsFormatted(data) {146 currentDetections = (data.detections || []).map(d => ({ ...d, defect_type_ar: getDefectTypeArabic(d.defect_type) }));147 currentParameters = data.parameters_used || {};148 if (currentImagePreview && currentDetections.length > 0) { drawBoundingBoxes(currentImagePreview, currentDetections).then(drawnImage => { annotatedImage.src = drawnImage; currentAnnotatedImage = drawnImage; }); }149 else if (currentImagePreview) annotatedImage.src = currentImagePreview;150 let detections = [...currentDetections].map(defect => { const severity = getSeverityInfo(defect.defect_type); const localRec = getLocalRecommendation(defect.defect_type); return { ...defect, severity_score: severity.score, severity_level: severity.level, severity_icon: severity.icon, severity_color: severity.color, description_ar: localRec.description_ar, recommendation_ar: localRec.recommendation_ar, priority: localRec.priority }; });151 detections.sort((a, b) => b.severity_score - a.severity_score);152 let statusHtml = `<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;"><span style="font-size: 1.3rem;">📋</span><div><strong style="color: #2563eb;">تقرير حالة اللوحة</strong><br>📌 عدد العيوب المكتشفة: ${detections.length}<br>⏱️ وقت التحليل: ${data.processing_time_ms?.toFixed(0) || '?'} مللي ثانية</div></div>`;153 if (detections.length > 0) { const severityCounts = { critical: detections.filter(d => d.severity_level === "critical").length, high: detections.filter(d => d.severity_level === "high").length, medium: detections.filter(d => d.severity_level === "medium").length, low: detections.filter(d => d.severity_level === "low").length }; statusHtml += `<div style="margin-top: 15px; display: flex; gap: 15px; flex-wrap: wrap;"><span>🔴 حرجة: ${severityCounts.critical}</span><span>🟠 عالية: ${severityCounts.high}</span><span>🟣 متوسطة: ${severityCounts.medium}</span><span>🟢 منخفضة: ${severityCounts.low}</span></div>`; }154 statusReportDiv.innerHTML = statusHtml;155 if (detections.length > 0) { defectsListDiv.innerHTML = ''; detections.forEach(defect => { const confidencePercent = (defect.confidence * 100).toFixed(1); const confLevel = getConfidenceLevel(defect.confidence); const severityClass = `severity-${defect.severity_level}`; defectsListDiv.innerHTML += `<div class="defect-item ${severityClass}"><div class="defect-header"><span class="defect-name"><span class="severity-icon">${defect.severity_icon}</span> ⚠️ ${defect.defect_type_ar || defect.defect_type}<span class="severity-score">الخطورة: ${defect.severity_score}/10</span></span><span class="confidence-box ${confLevel.class}">${confLevel.level}</span></div><div class="confidence-percent">🎯 نسبة الثقة: ${confidencePercent}%</div>${defect.description_ar ? `<div style="margin-top: 8px;">📝 ${defect.description_ar}</div>` : ''}${defect.recommendation_ar ? `<div class="recommendation" style="margin-top: 8px;">🛠️ التوصية: ${defect.recommendation_ar}</div>` : ''}</div>`; }); }156 else defectsListDiv.innerHTML = '<div class="summary-box">✅ لم يتم اكتشاف أي عيوب! اللوحة سليمة.</div>';157 const decision = calculateBoardDecision(detections);158 const recommendations = generateSmartRecommendations(detections);159 let summaryHtml = `<div class="summary-box"><div style="display: flex; justify-content: space-between; flex-wrap: wrap; gap: 15px; margin-bottom: 20px;"><div><strong>📌 عدد العيوب:</strong> ${detections.length}</div><div><strong>⏱️ وقت المعالجة:</strong> ${data.processing_time_ms?.toFixed(0) || '?'} مللي ثانية</div></div><div class="severity-stats"><div class="severity-stat"><span style="color:#ef4444;">🔴 حرجة</span>: ${detections.filter(d => d.severity_level === "critical").length}</div><div class="severity-stat"><span style="color:#f59e0b;">🟠 عالية</span>: ${detections.filter(d => d.severity_level === "high").length}</div><div class="severity-stat"><span style="color:#8b5cf6;">🟣 متوسطة</span>: ${detections.filter(d => d.severity_level === "medium").length}</div><div class="severity-stat"><span style="color:#10b981;">🟢 منخفضة</span>: ${detections.filter(d => d.severity_level === "low").length}</div></div><div class="board-decision ${decision.class}">🏷️ قرار اللوحة: ${decision.decision}<br><small>${decision.reason}</small></div>`;160 summaryHtml += `<div class="recommendations-section"><h4>🤖 التوصيات الذكية</h4><div class="risk-assessment risk-${recommendations.overall_risk_assessment.includes('حرج') ? 'critical' : recommendations.overall_risk_assessment.includes('مرتفع') ? 'high' : recommendations.overall_risk_assessment.includes('متوسط') ? 'medium' : 'low'}">📊 تقييم المخاطر: ${recommendations.overall_risk_assessment}${recommendations.estimated_repair_time ? `<br>⏱️ وقت الإصلاح المقدر: ${recommendations.estimated_repair_time}` : ''}</div><div class="analysis-text">📝 ${recommendations.analysis}</div>`;161 if (recommendations.repair_suggestions && recommendations.repair_suggestions.length > 0) { summaryHtml += `<div class="repair-suggestions"><strong>🔧 توصيات الإصلاح:</strong>`; recommendations.repair_suggestions.forEach(s => { summaryHtml += `<div class="suggestion priority-${s.priority}"><strong>${s.title}</strong><div>${s.description}</div><small>الأولوية: ${s.priority === 'critical' ? 'حرجة' : s.priority === 'high' ? 'عالية' : s.priority === 'medium' ? 'متوسطة' : 'منخفضة'}</small></div>`; }); summaryHtml += `</div>`; }162 if (recommendations.preventive_actions && recommendations.preventive_actions.length > 0) { summaryHtml += `<div class="preventive-actions" style="margin-top: 15px;"><strong>🛡️ إجراءات وقائية:</strong>`; recommendations.preventive_actions.forEach(p => { summaryHtml += `<div class="preventive-item priority-${p.priority}"><strong>${p.title}</strong><div>${p.description}</div></div>`; }); summaryHtml += `</div>`; }163 summaryHtml += `</div></div>`;164 summaryContentDiv.innerHTML = summaryHtml;165 resultsSection.style.display = 'block';166 resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });167}168 169// ========== Process Image ==========170async function processImage(file) {171 loadingDiv.style.display = 'block';172 resultsSection.style.display = 'none';173 const formData = new FormData();174 formData.append('file', file);175 formData.append('conf_threshold', document.getElementById('confThreshold')?.value || 0.5);176 formData.append('iou_threshold', document.getElementById('iouThreshold')?.value || 0.45);177 formData.append('imgsz', document.getElementById('imgSize')?.value || 640);178 formData.append('lang', document.getElementById('langSelect')?.value || 'ar');179 formData.append('classes_filter', document.getElementById('classesFilter')?.value || '');180 formData.append('return_annotated', 'false');181 try {182 const response = await fetch(DETECT_ENDPOINT, { method: 'POST', body: formData });183 if (!response.ok) throw new Error(`HTTP ${response.status}`);184 const data = await response.json();185 displayResultsFormatted(data);186 } catch (error) {187 console.error('Detection error:', error);188 defectsListDiv.innerHTML = `<div class="error-message">❌ فشل الاتصال: ${error.message}<br>تأكد من تشغيل الخادم المحلي</div>`;189 resultsSection.style.display = 'block';190 showToast("فشل الاتصال بالخادم", "error");191 } finally { loadingDiv.style.display = 'none'; }192}193 194// ========== Auth Functions ==========195function toggleAuthMode() {196 isLoginMode = !isLoginMode;197 const authTitle = document.getElementById('authTitle');198 const authSubmitBtn = document.getElementById('authSubmitBtn');199 const authSwitchBtn = document.getElementById('authSwitchBtn');200 const emailGroup = document.getElementById('emailGroup');201 if (isLoginMode) { authTitle.textContent = 'تسجيل الدخول'; authSubmitBtn.textContent = 'تسجيل الدخول'; authSwitchBtn.textContent = 'ليس لديك حساب؟ إنشاء حساب'; emailGroup.style.display = 'none'; }202 else { authTitle.textContent = 'إنشاء حساب'; authSubmitBtn.textContent = 'إنشاء حساب'; authSwitchBtn.textContent = 'لديك حساب؟ تسجيل الدخول'; emailGroup.style.display = 'block'; }203}204function openAuthModal() { const modal = document.getElementById('authModal'); if (modal) modal.style.display = 'flex'; }205function closeAuthModal() { const modal = document.getElementById('authModal'); if (modal) modal.style.display = 'none'; }206async function handleAuth(event) {207 event.preventDefault();208 const username = document.getElementById('loginUsername').value;209 const password = document.getElementById('loginPassword').value;210 const endpoint = isLoginMode ? LOGIN_ENDPOINT: REGISTER_ENDPOINT;211 let body = { username, password };212 if (!isLoginMode) { const email = document.getElementById('loginEmail').value; body = { username, email, password }; }213 try {214 const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });215 // console.log(response);216 const data = await response.json();217 if (response.ok) {218 localStorage.setItem('token', data.access_token);219 currentUser = data.user;220 const userNameSpan = document.getElementById('userName'); if (userNameSpan) userNameSpan.textContent = currentUser.username;221 const userBar = document.getElementById('userBar'); if (userBar) userBar.style.display = 'flex';222 closeAuthModal();223 showToast(`مرحباً ${currentUser.username}!`, 'success');224 loadBoards();225 } else showToast(data.detail || 'حدث خطأ', 'error');226 } catch (error) { showToast('خطأ في الاتصال', 'error'); }227}228function logout() { localStorage.removeItem('token'); currentUser = null; const userBar = document.getElementById('userBar'); if (userBar) userBar.style.display = 'none'; showToast('تم تسجيل الخروج', 'success'); openAuthModal(); }229 230// ========== Boards Functions ==========231async function saveCurrentBoard() {232 const token = localStorage.getItem('token');233 if (!token) {234 showToast('الرجاء تسجيل الدخول أولاً', 'error');235 openAuthModal();236 return;237 }238 239 if (!currentDetections.length) {240 showToast('لا توجد نتائج لحفظها', 'error');241 return;242 }243 244 const title = prompt('أدخل اسم اللوحة:', `PCB_${new Date().toLocaleDateString()}`);245 if (!title) return;246 247 // 1. إنشاء اللوحة248 const formData = new FormData();249 formData.append('title', title);250 formData.append('description', `تم اكتشاف ${currentDetections.length} عيب`);251 252 try {253 const createResponse = await fetch(`${API_BASE_URL}/api/boards`, {254 method: 'POST',255 headers: { 'Authorization': `Bearer ${token}` },256 body: formData257 });258 259 if (createResponse.ok) {260 const board = await createResponse.json();261 const boardId = board.id;262 263 // 2. تحديث اللوحة بالنتائج264 const updateFormData = new FormData();265 updateFormData.append('defects_data', JSON.stringify(currentDetections));266 updateFormData.append('report_data', JSON.stringify({267 summary: currentParameters,268 board_decision: calculateBoardDecision(currentDetections).decision,269 timestamp: new Date().toISOString()270 }));271 272 const updateResponse = await fetch(`${API_BASE_URL}/api/boards/${boardId}`, {273 method: 'PUT',274 headers: { 'Authorization': `Bearer ${token}` },275 body: updateFormData276 });277 278 if (updateResponse.ok) {279 showToast('تم حفظ اللوحة بنجاح', 'success');280 loadBoards();281 } else {282 showToast('تم إنشاء اللوحة ولكن فشل حفظ النتائج', 'warning');283 }284 } else {285 showToast('فشل حفظ اللوحة', 'error');286 }287 } catch (error) {288 console.error('Save board error:', error);289 showToast('خطأ في الاتصال', 'error');290 }291}292async function loadBoards() {293 const token = localStorage.getItem('token');294 if (!token) return;295 try {296 const response = await fetch(BOARDS_ENDPOINT, { headers: { 'Authorization': `Bearer ${token}` } });297 console.log(response);298 if (response.ok) { currentBoards = await response.json(); displayBoardsList(); }299 } catch (error) { console.error('Error loading boards:', error); }300}301function displayBoardsList() {302 let boardsHtml = '<div class="boards-list-container">';303 if (currentBoards.length === 0) boardsHtml += '<p style="text-align: center; color: #94a3b8;">لا توجد لوحات محفوظة</p>';304 else currentBoards.forEach(board => { boardsHtml += `<div class="board-item"><div onclick="viewBoard(${board.id})" style="flex: 1;"><div class="board-title">${board.title}</div><div class="board-date">${new Date(board.created_at).toLocaleDateString()}</div></div><button class="delete-board-btn" onclick="deleteBoard(${board.id})">🗑️</button></div>`; });305 boardsHtml += '</div>';306 let panel = document.getElementById('boardsPanel');307 if (!panel) { panel = document.createElement('div'); panel.id = 'boardsPanel'; panel.className = 'boards-panel'; panel.innerHTML = `<div class="boards-panel-header"><h3>📁 داراتي</h3><button onclick="closeBoardsPanel()">✖</button></div><div id="boardsListContent"></div>`; document.body.appendChild(panel); }308 document.getElementById('boardsListContent').innerHTML = boardsHtml;309 panel.classList.add('show');310}311function closeBoardsPanel() { const panel = document.getElementById('boardsPanel'); if (panel) panel.classList.remove('show'); }312function showBoards() { loadBoards(); }313async function viewBoard(boardId) {314 const token = localStorage.getItem('token');315 try {316 const response = await fetch(`${BOARDS_ENDPOINT}/${boardId}`, { headers: { 'Authorization': `Bearer ${token}` } });317 console.log(response);318 if (response.ok) {319 const board = await response.json();320 if (board.defects_data) { currentDetections = typeof board.defects_data === 'string' ? JSON.parse(board.defects_data) : board.defects_data; }321 if (board.annotated_image_path) { currentAnnotatedImage = board.annotated_image_path; annotatedImage.src = currentAnnotatedImage; }322 resultsSection.style.display = 'block';323 showToast(`تم تحميل اللوحة: ${board.title}`, 'success');324 closeBoardsPanel();325 }326 } catch (error) { showToast('خطأ في تحميل اللوحة', 'error'); }327}328async function deleteBoard(boardId) {329 if (!confirm('هل أنت متأكد من حذف هذه اللوحة؟')) return;330 const token = localStorage.getItem('token');331 try {332 const response = await fetch(`${BOARDS_ENDPOINT}/${boardId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } });333 console.log(response);334 if (response.ok) { showToast('تم حذف اللوحة', 'success'); loadBoards(); }335 } catch (error) { showToast('خطأ في حذف اللوحة', 'error'); }336}337 338// ========== Export Functions ==========339async function exportJSON() {340 if (!currentDetections.length) { showToast("لا توجد بيانات لتصديرها", "error"); return; }341 const reportData = { board_id: `PCB_${Date.now()}`, inspection_date: new Date().toISOString(), total_defects: currentDetections.length, defects: currentDetections.map(d => ({ type: d.defect_type, type_ar: d.defect_type_ar || getDefectTypeArabic(d.defect_type), confidence: d.confidence, severity: getSeverityInfo(d.defect_type).level, severity_score: getSeverityInfo(d.defect_type).score, bbox: d.bbox })), severity_summary: { critical: currentDetections.filter(d => getSeverityInfo(d.defect_type).level === "critical").length, high: currentDetections.filter(d => getSeverityInfo(d.defect_type).level === "high").length, medium: currentDetections.filter(d => getSeverityInfo(d.defect_type).level === "medium").length, low: currentDetections.filter(d => getSeverityInfo(d.defect_type).level === "low").length }, parameters_used: currentParameters, recommendations: generateSmartRecommendations(currentDetections), final_decision: calculateBoardDecision(currentDetections) };342 const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' });343 const url = URL.createObjectURL(blob);344 const a = document.createElement('a'); a.href = url; a.download = `pcb_report_${Date.now()}.json`; a.click();345 URL.revokeObjectURL(url);346 showToast("تم تصدير التقرير بنجاح بصيغة JSON", "success");347}348 349async function exportPDF() {350 const token = localStorage.getItem('token');351 if (!token) {352 showToast('الرجاء تسجيل الدخول أولاً', 'error');353 openAuthModal();354 return;355 }356 357 if (!currentFile) {358 showToast("لا توجد صورة لتحليلها", "error");359 return;360 }361 362 showToast("جاري إنشاء ملف PDF...", "info");363 364 const formData = new FormData();365 formData.append('file', currentFile);366 formData.append('conf_threshold', document.getElementById('confThreshold')?.value || 0.5);367 formData.append('iou_threshold', document.getElementById('iouThreshold')?.value || 0.45);368 formData.append('lang', document.getElementById('langSelect')?.value || 'ar');369 370 try {371 const response = await fetch(`${API_BASE_URL}/api/detect/pdf`, {372 method: 'POST',373 headers: { 'Authorization': `Bearer ${token}` },374 body: formData375 });376 console.log(response);377 if (response.ok) {378 const blob = await response.blob();379 const url = URL.createObjectURL(blob);380 const a = document.createElement('a');381 a.href = url;382 a.download = `pcb_report_${Date.now()}.pdf`;383 a.click();384 URL.revokeObjectURL(url);385 showToast("تم تصدير التقرير بنجاح", "success");386 } else {387 const error = await response.json();388 showToast(error.detail || "حدث خطأ أثناء إنشاء PDF", "error");389 }390 } catch (error) {391 console.error('PDF export error:', error);392 showToast("فشل إنشاء ملف PDF", "error");393 }394}395 396async function exportHTML() {397 if (!currentDetections.length) { showToast("لا توجد بيانات لتصديرها", "error"); return; }398 const decision = calculateBoardDecision(currentDetections);399 let defectsHtml = '';400 for (const d of currentDetections) { const severity = getSeverityInfo(d.defect_type); defectsHtml += `<tr style="border-bottom: 1px solid #e5e7eb;"><td style="padding: 12px;">${severity.icon} ${d.defect_type_ar || getDefectTypeArabic(d.defect_type)}</td><td style="padding: 12px;">${d.defect_type}</td><td style="padding: 12px;">${(d.confidence * 100).toFixed(1)}%</td><td style="padding: 12px;"><span style="background: ${severity.color}; color: white; padding: 4px 12px; border-radius: 20px;">${severity.level} (${severity.score}/10)</span></td></tr>`; }401 const htmlContent = `<!DOCTYPE html><html dir="rtl" lang="ar"><head><meta charset="UTF-8"><title>تقرير PCB - ${Date.now()}</title><style>body { font-family: 'Cairo', sans-serif; padding: 40px; background: #f8fafc; } .container { max-width: 1000px; margin: 0 auto; background: white; border-radius: 20px; padding: 30px; box-shadow: 0 10px 40px rgba(0,0,0,0.1); } h1 { color: #2563eb; text-align: center; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th { background: #2563eb; color: white; padding: 12px; text-align: right; } td { padding: 12px; border-bottom: 1px solid #e5e7eb; } .decision { padding: 15px; border-radius: 12px; margin: 20px 0; text-align: center; font-weight: bold; } .decision-pass { background: #d1fae5; color: #065f46; } .decision-conditional { background: #fed7aa; color: #9b2c1d; } .decision-fail { background: #fee2e2; color: #991b1b; } .footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; color: #64748b; }</style></head><body><div class="container"><h1>📋 تقرير فحص PCB</h1><p><strong>رقم التقرير:</strong> PCB_${Date.now()}</p><p><strong>تاريخ الفحص:</strong> ${new Date().toLocaleString('ar-SA')}</p><p><strong>إجمالي العيوب:</strong> ${currentDetections.length}</p><h2>🔍 العيوب المكتشفة</h2><table><thead><tr><th>نوع العيب</th><th>النوع (EN)</th><th>الثقة</th><th>الخطورة</th></tr></thead><tbody>${defectsHtml}</tbody></table><div class="decision ${decision.class}"><strong>القرار النهائي:</strong> ${decision.decision}</div><div class="footer"><p>تم إنشاء هذا التقرير بواسطة PCB Defect Detection System</p></div></div></body></html>`;402 const blob = new Blob([htmlContent], { type: 'text/html' });403 const url = URL.createObjectURL(blob);404 const a = document.createElement('a'); a.href = url; a.download = `pcb_report_${Date.now()}.html`; a.click();405 URL.revokeObjectURL(url);406 showToast("تم تصدير التقرير بنجاح بصيغة HTML", "success");407}408function downloadAnnotatedImage() { if (!currentAnnotatedImage) { showToast("لا توجد صورة للتحميل", "error"); return; } const link = document.createElement('a'); link.href = currentAnnotatedImage; link.download = `pcb_annotated_${Date.now()}.jpg`; link.click(); showToast("تم تحميل الصورة بنجاح", "success"); }409async function copyReportToClipboard() { if (!currentDetections.length) { showToast("لا توجد بيانات لنسخها", "error"); return; } const reportText = currentDetections.map(d => { const defectName = d.defect_type_ar || getDefectTypeArabic(d.defect_type); return `${defectName}: ${(d.confidence * 100).toFixed(1)}%`; }).join('\n'); const fullReport = `🔬 تقرير فحص PCB\n${'='.repeat(30)}\n${reportText}\n${'='.repeat(30)}\n📅 ${new Date().toLocaleString('ar-SA')}`; try { await navigator.clipboard.writeText(fullReport); showToast("تم نسخ التقرير إلى الحافظة", "success"); } catch (err) { showToast("فشل النسخ إلى الحافظة", "error"); } }410async function shareResults() { if (!currentDetections.length) { showToast("لا توجد نتائج للمشاركة", "error"); return; } const shareData = { title: 'PCB Defect Detection Report', text: `تم اكتشاف ${currentDetections.length} عيب في اللوحة`, url: window.location.href }; if (navigator.share) { try { await navigator.share(shareData); showToast("تمت المشاركة", "success"); } catch (err) { if (err.name !== 'AbortError') { await navigator.clipboard.writeText(window.location.href); showToast("تم نسخ رابط الموقع", "success"); } } } else { await navigator.clipboard.writeText(window.location.href); showToast("تم نسخ رابط الموقع", "success"); } }411 412// ========== Settings ==========413function toggleSettings() { const panel = document.getElementById('advancedPanel'); const icon = document.getElementById('settingsIcon'); panel.classList.toggle('show'); icon.classList.toggle('rotated'); }414function updateSettingValues() {415 const confSlider = document.getElementById('confThreshold');416 const iouSlider = document.getElementById('iouThreshold');417 const confValue = document.getElementById('confValue');418 const iouValue = document.getElementById('iouValue');419 if (confSlider && confValue) confSlider.addEventListener('input', () => { confValue.textContent = confSlider.value; });420 if (iouSlider && iouValue) iouSlider.addEventListener('input', () => { iouValue.textContent = iouSlider.value; });421}422// ========== Dashboard Functions ==========423let dashboardChart = null;424let severityChart = null;425let isDashboardOpen = false;426 427async function loadDashboard() {428 const token = localStorage.getItem('token');429 if (!token) {430 showToast('الرجاء تسجيل الدخول أولاً', 'error');431 return;432 }433 434 try {435 const response = await fetch(`${API_BASE_URL}/api/stats`, {436 headers: { 'Authorization': `Bearer ${token}` }437 });438 console.log(response);439 if (response.ok) {440 const stats = await response.json();441 displayDashboardStats(stats);442 } else {443 showToast('فشل تحميل الإحصائيات', 'error');444 }445 } catch (error) {446 console.error('Error loading dashboard:', error);447 showToast('خطأ في تحميل الإحصائيات', 'error');448 }449}450 451function displayDashboardStats(stats) {452 // عرض البطاقات الإحصائية453 document.getElementById('statsGrid').innerHTML = `454 <div class="stat-card"><h3>${stats.total_boards}</h3><p>📁 إجمالي اللوحات</p></div>455 <div class="stat-card"><h3>${stats.total_defects}</h3><p>🔍 إجمالي العيوب</p></div>456 <div class="stat-card"><h3>${stats.model_accuracy}%</h3><p>🎯 دقة النموذج</p></div>457 <div class="stat-card"><h3>${Object.keys(stats.defect_counts).length}</h3><p>🏷️ أنواع العيوب</p></div>458 `;459 460 // رسم بياني للعيوب حسب النوع461 const ctx1 = document.getElementById('defectsChart').getContext('2d');462 if (dashboardChart) dashboardChart.destroy();463 dashboardChart = new Chart(ctx1, {464 type: 'bar',465 data: {466 labels: Object.keys(stats.defect_counts),467 datasets: [{ label: 'عدد العيوب', data: Object.values(stats.defect_counts), backgroundColor: '#2563eb' }]468 },469 options: { responsive: true, maintainAspectRatio: true }470 });471 472 // رسم بياني للخطورة473 const ctx2 = document.getElementById('severityChart').getContext('2d');474 if (severityChart) severityChart.destroy();475 severityChart = new Chart(ctx2, {476 type: 'doughnut',477 data: {478 labels: ['حرجة', 'عالية', 'متوسطة', 'منخفضة'],479 datasets: [{ data: [stats.severity_counts.critical, stats.severity_counts.high, stats.severity_counts.medium, stats.severity_counts.low], backgroundColor: ['#ef4444', '#f59e0b', '#8b5cf6', '#10b981'] }]480 },481 options: { responsive: true }482 });483 484 // عرض أكثر العيوب شيوعاً485 document.getElementById('mostCommonList').innerHTML = stats.most_common_defects.map(d => `486 <div class="defect-stat-item"><div class="defect-name"><span>🏆</span><span>${d.type}</span></div><div class="defect-count">${d.count} عيب</div></div>487 `).join('');488}489 490function toggleDashboard() {491 const section = document.getElementById('dashboardSection');492 const btn = document.getElementById('dashboardToggleBtn');493 494 if (isDashboardOpen) {495 section.style.display = 'none';496 btn.innerHTML = '<i class="fas fa-chart-line"></i> عرض الإحصائيات';497 isDashboardOpen = false;498 } else {499 section.style.display = 'block';500 btn.innerHTML = '<i class="fas fa-times"></i> إخفاء الإحصائيات';501 loadDashboard();502 isDashboardOpen = true;503 }504}505 506// ========== Batch Processing Variables ==========507let batchFiles = [];508let batchResults = [];509let isBatchMode = false;510let batchPreviews = [];511 512// ========== Batch Processing Functions ==========513function toggleBatchMode() {514 const batchContainer = document.getElementById('batchContainer');515 const batchContent = document.getElementById('batchContent');516 const toggleBtn = document.getElementById('toggleBatchBtn');517 518 if (isBatchMode) {519 batchContainer.style.display = 'none';520 isBatchMode = false;521 } else {522 batchContainer.style.display = 'block';523 isBatchMode = true;524 if (batchContent.style.display === 'none') {525 batchContent.style.display = 'block';526 toggleBtn.innerHTML = '<i class="fas fa-chevron-up"></i> إظهار/إخفاء';527 }528 }529}530 531function toggleBatchContent() {532 const batchContent = document.getElementById('batchContent');533 const toggleBtn = document.getElementById('toggleBatchBtn');534 535 if (batchContent.style.display === 'none') {536 batchContent.style.display = 'block';537 toggleBtn.innerHTML = '<i class="fas fa-chevron-up"></i> إظهار/إخفاء';538 } else {539 batchContent.style.display = 'none';540 toggleBtn.innerHTML = '<i class="fas fa-chevron-down"></i> إظهار/إخفاء';541 }542}543 544function updateBatchPreview() {545 const previewList = document.getElementById('batchPreviewList');546 previewList.innerHTML = '';547 548 batchPreviews.forEach((preview, index) => {549 const item = document.createElement('div');550 item.className = 'batch-preview-item';551 item.innerHTML = `552 <img src="${preview.data}" alt="معاينة">553 <div class="remove-preview" onclick="removeBatchImage(${index})">✖</div>554 `;555 previewList.appendChild(item);556 });557 558 if (batchPreviews.length === 0) {559 previewList.innerHTML = '<p style="color: #64748b; text-align: center;">لا توجد صور مختارة</p>';560 }561}562 563function removeBatchImage(index) {564 batchFiles.splice(index, 1);565 batchPreviews.splice(index, 1);566 updateBatchPreview();567}568 569async function processBatch() {570 if (batchFiles.length === 0) {571 showToast('الرجاء اختيار صور أولاً', 'error');572 return;573 }574 575 const token = localStorage.getItem('token');576 if (!token) {577 showToast('الرجاء تسجيل الدخول أولاً', 'error');578 openAuthModal();579 return;580 }581 582 const progressContainer = document.getElementById('batchProgress');583 const progressFill = document.getElementById('batchProgressFill');584 const progressText = document.getElementById('batchProgressText');585 const processBtn = document.getElementById('processBatchBtn');586 const summaryDiv = document.getElementById('batchSummary');587 const resultsList = document.getElementById('batchResultsList');588 589 progressContainer.style.display = 'block';590 processBtn.disabled = true;591 summaryDiv.style.display = 'none';592 resultsList.innerHTML = '';593 batchResults = [];594 595 const total = batchFiles.length;596 let completed = 0;597 let totalDefects = 0;598 let successfulCount = 0;599 600 // الحصول على إعدادات الكشف601 const confThreshold = document.getElementById('confThreshold')?.value || 0.5;602 const iouThreshold = document.getElementById('iouThreshold')?.value || 0.45;603 const imgSize = document.getElementById('imgSize')?.value || 640;604 const lang = document.getElementById('langSelect')?.value || 'ar';605 606 for (let i = 0; i < total; i++) {607 const file = batchFiles[i];608 progressText.innerHTML = `جاري معالجة ${i + 1} من ${total}: ${file.name}`;609 progressFill.style.width = `${(completed / total) * 100}%`;610 611 const formData = new FormData();612 formData.append('file', file);613 formData.append('conf_threshold', confThreshold);614 formData.append('iou_threshold', iouThreshold);615 formData.append('imgsz', imgSize);616 formData.append('lang', lang);617 formData.append('return_annotated', 'false');618 619 try {620 const response = await fetch(`${API_BASE_URL}/api/detect`, {621 method: 'POST',622 headers: { 'Authorization': `Bearer ${token}` },623 body: formData624 });625 console.log(response);626 const data = await response.json();627 628 if (data.success) {629 successfulCount++;630 totalDefects += data.num_defects;631 batchResults.push({632 filename: file.name,633 success: true,634 num_defects: data.num_defects,635 detections: data.detections,636 board_decision: data.board_decision,637 annotated_image: data.annotated_image_base64638 });639 640 // عرض النتيجة641 const resultCard = document.createElement('div');642 resultCard.className = 'batch-result-card success';643 resultCard.innerHTML = `644 <div class="batch-result-header">645 <span class="batch-result-filename">${file.name}</span>646 <span class="batch-result-defects">${data.num_defects} عيب</span>647 </div>648 <div class="batch-result-decision" style="background: ${data.board_decision.includes('مقبول') ? '#10b98120' : '#ef444420'}; color: ${data.board_decision.includes('مقبول') ? '#10b981' : '#ef4444'}">649 ${data.board_decision}650 </div>651 `;652 resultCard.onclick = () => showBatchResultDetail(data, file.name);653 resultsList.appendChild(resultCard);654 } else {655 batchResults.push({656 filename: file.name,657 success: false,658 error: data.detail || 'فشل المعالجة'659 });660 661 const resultCard = document.createElement('div');662 resultCard.className = 'batch-result-card failed';663 resultCard.innerHTML = `664 <div class="batch-result-header">665 <span class="batch-result-filename">${file.name}</span>666 <span class="batch-result-defects" style="color: #ef4444;">فشل</span>667 </div>668 <div class="batch-result-decision">${data.detail || 'فشل المعالجة'}</div>669 `;670 resultsList.appendChild(resultCard);671 }672 } catch (error) {673 batchResults.push({674 filename: file.name,675 success: false,676 error: error.message677 });678 679 const resultCard = document.createElement('div');680 resultCard.className = 'batch-result-card failed';681 resultCard.innerHTML = `682 <div class="batch-result-header">683 <span class="batch-result-filename">${file.name}</span>684 <span class="batch-result-defects" style="color: #ef4444;">خطأ</span>685 </div>686 <div class="batch-result-decision">${error.message}</div>687 `;688 resultsList.appendChild(resultCard);689 }690 691 completed++;692 progressFill.style.width = `${(completed / total) * 100}%`;693 }694 695 progressFill.style.width = '100%';696 progressText.innerHTML = `اكتملت المعالجة! تم معالجة ${total} صورة`;697 698 setTimeout(() => {699 progressContainer.style.display = 'none';700 }, 2000);701 702 processBtn.disabled = false;703 704 // عرض الملخص705 summaryDiv.style.display = 'block';706 summaryDiv.innerHTML = `707 <div class="batch-summary">708 <h4>📊 ملخص المعالجة</h4>709 <div class="batch-summary-stats">710 <div class="batch-summary-stat">711 <div style="font-size: 24px; font-weight: bold;">${total}</div>712 <div>إجمالي الصور</div>713 </div>714 <div class="batch-summary-stat">715 <div style="font-size: 24px; font-weight: bold; color: #10b981;">${successfulCount}</div>716 <div>صور ناجحة</div>717 </div>718 <div class="batch-summary-stat">719 <div style="font-size: 24px; font-weight: bold; color: #ef4444;">${total - successfulCount}</div>720 <div>صور فاشلة</div>721 </div>722 <div class="batch-summary-stat">723 <div style="font-size: 24px; font-weight: bold; color: #2563eb;">${totalDefects}</div>724 <div>إجمالي العيوب</div>725 </div>726 </div>727 <button id="exportBatchReportBtn" class="batch-export-btn" onclick="exportBatchReport()">728 <i class="fas fa-download"></i> تصدير التقرير المجمع729 </button>730 </div>731 `;732}733 734function showBatchResultDetail(data, filename) {735 // عرض تفاصيل الصورة في نافذة منبثقة736 const modal = document.createElement('div');737 modal.className = 'modal';738 modal.style.display = 'flex';739 modal.innerHTML = `740 <div class="modal-content" style="max-width: 600px;">741 <span class="close-auth" onclick="this.parentElement.parentElement.remove()">×</span>742 <h3>${filename}</h3>743 ${data.annotated_image ? `<img src="data:image/jpeg;base64,${data.annotated_image}" style="width: 100%; border-radius: 12px; margin: 15px 0;">` : ''}744 <div class="decision ${data.board_decision.includes('مقبول') ? 'decision-pass' : 'decision-fail'}">745 ${data.board_decision}746 </div>747 <div class="defects-list">748 ${data.detections.map(d => `<div class="defect-item">${d.defect_type_ar || d.defect_type} (${(d.confidence * 100).toFixed(1)}%)</div>`).join('')}749 </div>750 <button class="btn-primary" onclick="this.parentElement.parentElement.remove()">إغلاق</button>751 </div>752 `;753 document.body.appendChild(modal);754}755 756function exportBatchReport() {757 if (batchResults.length === 0) {758 showToast('لا توجد نتائج لتصديرها', 'error');759 return;760 }761 762 const reportData = {763 export_date: new Date().toISOString(),764 total_images: batchResults.length,765 successful_count: batchResults.filter(r => r.success).length,766 failed_count: batchResults.filter(r => !r.success).length,767 total_defects: batchResults.filter(r => r.success).reduce((sum, r) => sum + (r.num_defects || 0), 0),768 results: batchResults769 };770 771 const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' });772 const url = URL.createObjectURL(blob);773 const a = document.createElement('a');774 a.href = url;775 a.download = `batch_report_${Date.now()}.json`;776 a.click();777 URL.revokeObjectURL(url);778 779 showToast("تم تصدير التقرير المجمع", "success");780}781 782// ========== Batch Event Listeners ==========783function initBatchEvents() {784 const batchUploadArea = document.getElementById('batchUploadArea');785 const batchFileInput = document.getElementById('batchFileInput');786 const batchSelectBtn = document.getElementById('batchSelectBtn');787 const processBatchBtn = document.getElementById('processBatchBtn');788 const toggleBatchBtn = document.getElementById('toggleBatchBtn');789 790 if (batchSelectBtn) {791 batchSelectBtn.onclick = () => batchFileInput.click();792 }793 794 if (batchUploadArea) {795 batchUploadArea.onclick = (e) => {796 if (e.target === batchUploadArea || batchUploadArea.contains(e.target)) {797 batchFileInput.click();798 }799 };800 801 batchUploadArea.ondragover = (e) => {802 e.preventDefault();803 batchUploadArea.style.borderColor = '#60a5fa';804 };805 806 batchUploadArea.ondragleave = () => {807 batchUploadArea.style.borderColor = '#2563eb';808 };809 810 batchUploadArea.ondrop = (e) => {811 e.preventDefault();812 batchUploadArea.style.borderColor = '#2563eb';813 const files = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));814 if (files.length) {815 addBatchFiles(files);816 }817 };818 }819 820 if (batchFileInput) {821 batchFileInput.onchange = (e) => {822 if (e.target.files.length) {823 addBatchFiles(Array.from(e.target.files));824 batchFileInput.value = '';825 }826 };827 }828 829 if (processBatchBtn) {830 processBatchBtn.onclick = processBatch;831 }832 833 if (toggleBatchBtn) {834 toggleBatchBtn.onclick = toggleBatchContent;835 }836}837 838function addBatchFiles(files) {839 files.forEach(file => {840 if (file.type.startsWith('image/')) {841 batchFiles.push(file);842 const reader = new FileReader();843 reader.onload = (e) => {844 batchPreviews.push({ data: e.target.result });845 updateBatchPreview();846 };847 reader.readAsDataURL(file);848 }849 });850 showToast(`تمت إضافة ${files.length} صورة`, 'success');851}852 853// ========== Dark Mode Functions ==========854function initDarkMode() {855 const savedMode = localStorage.getItem('darkMode');856 const darkModeToggle = document.getElementById('darkModeToggle');857 const darkModeText = document.getElementById('darkModeText');858 859 if (savedMode === 'enabled') {860 document.body.classList.add('dark-mode');861 if (darkModeText) darkModeText.textContent = 'وضع فاتح';862 if (darkModeToggle) darkModeToggle.innerHTML = '<i class="fas fa-sun"></i> <span id="darkModeText">وضع فاتح</span>';863 } else {864 document.body.classList.remove('dark-mode');865 if (darkModeText) darkModeText.textContent = 'وضع ليلي';866 if (darkModeToggle) darkModeToggle.innerHTML = '<i class="fas fa-moon"></i> <span id="darkModeText">وضع ليلي</span>';867 }868}869 870function toggleDarkMode() {871 const darkModeText = document.getElementById('darkModeText');872 const darkModeToggle = document.getElementById('darkModeToggle');873 874 if (document.body.classList.contains('dark-mode')) {875 document.body.classList.remove('dark-mode');876 localStorage.setItem('darkMode', 'disabled');877 if (darkModeText) darkModeText.textContent = 'وضع ليلي';878 if (darkModeToggle) darkModeToggle.innerHTML = '<i class="fas fa-moon"></i> <span id="darkModeText">وضع ليلي</span>';879 showToast('تم التبديل إلى الوضع الفاتح', 'success');880 } else {881 document.body.classList.add('dark-mode');882 localStorage.setItem('darkMode', 'enabled');883 if (darkModeText) darkModeText.textContent = 'وضع فاتح';884 if (darkModeToggle) darkModeToggle.innerHTML = '<i class="fas fa-sun"></i> <span id="darkModeText">وضع فاتح</span>';885 showToast('تم التبديل إلى الوضع الليلي', 'success');886 }887}888 889// ========== Event Listeners ==========890document.addEventListener('DOMContentLoaded', function () {891 892 // Dark Mode Toggle893const darkModeToggle = document.getElementById('darkModeToggle');894if (darkModeToggle) {895 darkModeToggle.addEventListener('click', toggleDarkMode);896}897initDarkMode();898 // تهيئة المعالجة الدفعية899initBatchEvents();900 901// إظهار/إخفاء وضع الدفعات (اختياري)902const toggleBatchModeBtn = document.getElementById('toggleBatchMode');903if (toggleBatchModeBtn) {904 toggleBatchModeBtn.addEventListener('click', toggleBatchMode);905}906 updateSettingValues();907 uploadBtn.addEventListener('click', () => fileInput.click());908 uploadArea.addEventListener('click', (e) => { if (e.target === uploadArea || e.target.classList.contains('upload-icon') || e.target.tagName === 'H3' || e.target.tagName === 'P') fileInput.click(); });909 fileInput.addEventListener('change', (e) => { if (e.target.files && e.target.files[0]) { currentFile = e.target.files[0]; const reader = new FileReader(); reader.onload = (event) => { currentImagePreview = event.target.result; previewImg.src = event.target.result; previewImg.style.display = 'inline-block'; defectBtn.style.display = 'inline-block'; }; reader.readAsDataURL(currentFile); } });910 defectBtn.addEventListener('click', async () => { if (!currentFile) { showToast("الرجاء اختيار صورة أولاً", "error"); return; } await processImage(currentFile); });911 document.getElementById('saveBoardBtn')?.addEventListener('click', saveCurrentBoard);912 document.getElementById('shareResultsBtn')?.addEventListener('click', shareResults);913 document.getElementById('copyReportBtn')?.addEventListener('click', copyReportToClipboard);914 document.getElementById('exportJsonBtn')?.addEventListener('click', exportJSON);915 document.getElementById('exportHtmlBtn')?.addEventListener('click', exportHTML);916 document.getElementById('downloadImageBtn')?.addEventListener('click', downloadAnnotatedImage);917 const token = localStorage.getItem('token');918 if (token) { fetch('/api/me', { headers: { 'Authorization': `Bearer ${token}` } }).then(res => res.json()).then(user => { currentUser = user; document.getElementById('userName').textContent = currentUser.username; document.getElementById('userBar').style.display = 'flex'; }).catch(() => { localStorage.removeItem('token'); openAuthModal(); }); }919 else openAuthModal();920 921const dashboardToggleBtn = document.getElementById('dashboardToggleBtn');922if (dashboardToggleBtn) {923 dashboardToggleBtn.addEventListener('click', toggleDashboard);924}925 926const exportPdfBtn = document.getElementById('exportPdfBtn');927if (exportPdfBtn) {928 exportPdfBtn.addEventListener('click', exportPDF);929}930});