CoolFace
Apppublic

pcb-defect-detector-project/new-version

sourceHugging Faceupdated 26d agoView on Hugging Face
0likes
script.js930 linesDownload Raw Back to templates
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()">&times;</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});