Spaces:
Paused
Paused
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>آپلود فایل | سامانه حقوقی</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@200;300;400;500;600;700;800;900&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> | |
| <!-- Load API Client --> | |
| <script src="/static/js/api-client.js"></script> | |
| <script src="/static/js/core.js"></script> | |
| <style> | |
| /* استفاده از همان CSS از upload.html قبلی */ | |
| :root { | |
| --text-primary: #0f172a; | |
| --text-secondary: #475569; | |
| --text-muted: #64748b; | |
| --text-light: #ffffff; | |
| --body-bg: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 50%, #cbd5e1 100%); | |
| --card-bg: rgba(255, 255, 255, 0.95); | |
| --glass-bg: rgba(255, 255, 255, 0.9); | |
| --glass-border: rgba(148, 163, 184, 0.2); | |
| --primary-gradient: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); | |
| --secondary-gradient: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); | |
| --success-gradient: linear-gradient(135deg, #10b981 0%, #047857 100%); | |
| --warning-gradient: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
| --danger-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); | |
| --shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.05); | |
| --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| --shadow-md: 0 4px 15px rgba(0, 0, 0, 0.1); | |
| --shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.12); | |
| --sidebar-width: 260px; | |
| --border-radius: 12px; | |
| --border-radius-sm: 8px; | |
| --transition-smooth: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); | |
| --transition-fast: all 0.15s ease-in-out; | |
| --font-size-xs: 0.7rem; | |
| --font-size-sm: 0.8rem; | |
| --font-size-base: 0.9rem; | |
| --font-size-lg: 1.1rem; | |
| --font-size-xl: 1.25rem; | |
| --font-size-2xl: 1.5rem; | |
| } | |
| /* Simplified CSS - using key styles only */ | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: var(--body-bg); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| overflow-x: hidden; | |
| font-size: var(--font-size-base); | |
| } | |
| .dashboard-container { | |
| display: flex; | |
| min-block-size: 100vh; | |
| inline-size: 100%; | |
| } | |
| /* سایدبار کامپکت */ | |
| .sidebar { | |
| inline-size: var(--sidebar-width); | |
| background: linear-gradient(135deg, | |
| rgba(248, 250, 252, 0.98) 0%, | |
| rgba(241, 245, 249, 0.95) 25%, | |
| rgba(226, 232, 240, 0.98) 50%, | |
| rgba(203, 213, 225, 0.95) 75%, | |
| rgba(148, 163, 184, 0.1) 100%); | |
| backdrop-filter: blur(25px); | |
| padding: 1rem 0; | |
| position: fixed; | |
| block-size: 100vh; | |
| inset-inline-end: 0; | |
| inset-block-start: 0; | |
| z-index: 1000; | |
| overflow-y: auto; | |
| box-shadow: -8px 0 32px rgba(59, 130, 246, 0.12); | |
| border-inline-start: 1px solid rgba(59, 130, 246, 0.15); | |
| } | |
| .sidebar-header { | |
| padding: 0 1rem 1rem; | |
| border-block-end: 1px solid rgba(59, 130, 246, 0.12); | |
| margin-block-end: 1rem; | |
| display: flex; | |
| align-items: center; | |
| background: linear-gradient(135deg, | |
| rgba(255, 255, 255, 0.4) 0%, | |
| rgba(248, 250, 252, 0.2) 100%); | |
| margin: 0 0.5rem 1rem; | |
| border-radius: var(--border-radius); | |
| backdrop-filter: blur(10px); | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| color: var(--text-primary); | |
| text-decoration: none; | |
| } | |
| .logo-icon { | |
| inline-size: 2rem; | |
| block-size: 2rem; | |
| background: var(--primary-gradient); | |
| border-radius: var(--border-radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1rem; | |
| color: white; | |
| } | |
| .logo-text { | |
| font-size: var(--font-size-lg); | |
| font-weight: 700; | |
| background: var(--primary-gradient); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .nav-section { | |
| margin-block-end: 1rem; | |
| } | |
| .nav-title { | |
| padding: 0 1rem 0.4rem; | |
| font-size: var(--font-size-xs); | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| color: var(--text-secondary); | |
| } | |
| .nav-menu { | |
| list-style: none; | |
| } | |
| .nav-item { | |
| margin: 0.15rem 0.5rem; | |
| } | |
| .nav-link { | |
| display: flex; | |
| align-items: center; | |
| padding: 0.6rem 0.8rem; | |
| color: var(--text-primary); | |
| text-decoration: none; | |
| border-radius: var(--border-radius-sm); | |
| transition: var(--transition-smooth); | |
| font-weight: 500; | |
| font-size: var(--font-size-sm); | |
| cursor: pointer; | |
| border: 1px solid transparent; | |
| } | |
| .nav-link:hover { | |
| transform: translateX(-2px); | |
| border-color: rgba(59, 130, 246, 0.15); | |
| background: rgba(59, 130, 246, 0.05); | |
| } | |
| .nav-link.active { | |
| background: var(--primary-gradient); | |
| color: var(--text-light); | |
| box-shadow: var(--shadow-md); | |
| } | |
| .nav-icon { | |
| margin-inline-start: 0.6rem; | |
| inline-size: 1rem; | |
| text-align: center; | |
| font-size: 0.9rem; | |
| } | |
| .nav-badge { | |
| background: var(--danger-gradient); | |
| color: white; | |
| padding: 0.15rem 0.4rem; | |
| border-radius: 10px; | |
| font-size: var(--font-size-xs); | |
| font-weight: 600; | |
| margin-inline-end: auto; | |
| min-inline-size: 1.2rem; | |
| text-align: center; | |
| } | |
| /* محتوای اصلی */ | |
| .main-content { | |
| flex: 1; | |
| margin-inline-end: var(--sidebar-width); | |
| padding: 1rem; | |
| min-block-size: 100vh; | |
| inline-size: calc(100% - var(--sidebar-width)); | |
| } | |
| .page-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-block-end: 2rem; | |
| padding: 1rem 0; | |
| border-block-end: 1px solid rgba(0, 0, 0, 0.1); | |
| } | |
| .page-title { | |
| font-size: var(--font-size-2xl); | |
| font-weight: 800; | |
| background: var(--primary-gradient); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| } | |
| .page-actions { | |
| display: flex; | |
| gap: 0.8rem; | |
| } | |
| .btn { | |
| padding: 0.6rem 1.2rem; | |
| border: none; | |
| border-radius: var(--border-radius-sm); | |
| font-family: inherit; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition-smooth); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| text-decoration: none; | |
| font-size: var(--font-size-sm); | |
| } | |
| .btn-outline { | |
| background: transparent; | |
| color: var(--text-primary); | |
| border: 1px solid rgba(59, 130, 246, 0.2); | |
| } | |
| .btn-outline:hover { | |
| background: rgba(59, 130, 246, 0.05); | |
| border-color: rgba(59, 130, 246, 0.4); | |
| } | |
| /* آپلود فایل */ | |
| .upload-section { | |
| background: var(--card-bg); | |
| backdrop-filter: blur(10px); | |
| border-radius: var(--border-radius); | |
| padding: 2rem; | |
| margin-block-end: 2rem; | |
| box-shadow: var(--shadow-md); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .upload-area { | |
| border: 3px dashed rgba(59, 130, 246, 0.3); | |
| border-radius: var(--border-radius); | |
| padding: 3rem; | |
| text-align: center; | |
| background: linear-gradient(135deg, rgba(59, 130, 246, 0.02), rgba(255, 255, 255, 0.1)); | |
| transition: var(--transition-smooth); | |
| cursor: pointer; | |
| position: relative; | |
| } | |
| .upload-area:hover, | |
| .upload-area.dragover { | |
| border-color: rgba(59, 130, 246, 0.6); | |
| background: linear-gradient(135deg, rgba(59, 130, 246, 0.05), rgba(255, 255, 255, 0.15)); | |
| transform: scale(1.01); | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .upload-icon { | |
| font-size: 4rem; | |
| color: rgba(59, 130, 246, 0.6); | |
| margin-bottom: 1.5rem; | |
| transition: var(--transition-smooth); | |
| } | |
| .upload-area:hover .upload-icon { | |
| color: rgba(59, 130, 246, 0.8); | |
| transform: scale(1.1); | |
| } | |
| .upload-title { | |
| font-size: var(--font-size-xl); | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| margin-bottom: 0.8rem; | |
| } | |
| .upload-description { | |
| color: var(--text-secondary); | |
| margin-bottom: 1.5rem; | |
| font-size: var(--font-size-base); | |
| line-height: 1.6; | |
| } | |
| .upload-input { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0; | |
| cursor: pointer; | |
| } | |
| .upload-btn { | |
| background: var(--primary-gradient); | |
| color: white; | |
| border: none; | |
| padding: 0.8rem 2rem; | |
| border-radius: var(--border-radius-sm); | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition-smooth); | |
| font-size: var(--font-size-base); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| margin: 0 auto; | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .upload-btn:hover { | |
| box-shadow: var(--shadow-lg); | |
| transform: translateY(-2px); | |
| } | |
| /* فایلهای آپلود شده */ | |
| .upload-files { | |
| margin-top: 2rem; | |
| display: none; | |
| } | |
| .upload-files.has-files { | |
| display: block; | |
| } | |
| .files-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| padding-bottom: 0.8rem; | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.1); | |
| } | |
| .files-title { | |
| font-size: var(--font-size-lg); | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| } | |
| .files-actions { | |
| display: flex; | |
| gap: 0.6rem; | |
| } | |
| .btn-sm { | |
| padding: 0.4rem 0.8rem; | |
| font-size: var(--font-size-xs); | |
| } | |
| .btn-success { | |
| background: var(--success-gradient); | |
| color: white; | |
| } | |
| .btn-danger { | |
| background: var(--danger-gradient); | |
| color: white; | |
| } | |
| .file-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| padding: 1rem; | |
| background: rgba(255, 255, 255, 0.7); | |
| border-radius: var(--border-radius-sm); | |
| margin-bottom: 0.8rem; | |
| border: 1px solid rgba(0, 0, 0, 0.05); | |
| transition: var(--transition-smooth); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .file-item::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| bottom: 0; | |
| width: 4px; | |
| background: var(--primary-gradient); | |
| } | |
| .file-item:hover { | |
| background: rgba(255, 255, 255, 0.9); | |
| transform: translateX(-3px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .file-icon { | |
| width: 3rem; | |
| height: 3rem; | |
| background: var(--danger-gradient); | |
| border-radius: var(--border-radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-size: 1.2rem; | |
| flex-shrink: 0; | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .file-info { | |
| flex: 1; | |
| } | |
| .file-name { | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| font-size: var(--font-size-base); | |
| margin-bottom: 0.3rem; | |
| } | |
| .file-details { | |
| display: flex; | |
| gap: 1rem; | |
| font-size: var(--font-size-sm); | |
| color: var(--text-secondary); | |
| } | |
| .file-progress { | |
| width: 100%; | |
| height: 6px; | |
| background: rgba(0, 0, 0, 0.08); | |
| border-radius: 3px; | |
| margin-top: 0.8rem; | |
| overflow: hidden; | |
| } | |
| .file-progress-bar { | |
| height: 100%; | |
| background: var(--success-gradient); | |
| border-radius: 3px; | |
| transition: width 0.3s ease; | |
| position: relative; | |
| } | |
| .file-progress-bar::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); | |
| animation: shimmer 2s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .file-status { | |
| font-size: var(--font-size-xs); | |
| font-weight: 600; | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 15px; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.3rem; | |
| white-space: nowrap; | |
| } | |
| .file-status.uploading { | |
| background: rgba(245, 158, 11, 0.1); | |
| color: #b45309; | |
| } | |
| .file-status.processing { | |
| background: rgba(59, 130, 246, 0.1); | |
| color: #1d4ed8; | |
| } | |
| .file-status.success { | |
| background: rgba(16, 185, 129, 0.1); | |
| color: #047857; | |
| } | |
| .file-status.error { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: #b91c1c; | |
| } | |
| .file-actions { | |
| display: flex; | |
| gap: 0.4rem; | |
| flex-shrink: 0; | |
| } | |
| .action-btn { | |
| width: 2rem; | |
| height: 2rem; | |
| border: none; | |
| border-radius: var(--border-radius-sm); | |
| cursor: pointer; | |
| transition: var(--transition-fast); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: var(--font-size-xs); | |
| } | |
| .action-btn.retry { | |
| background: rgba(245, 158, 11, 0.1); | |
| color: #f59e0b; | |
| } | |
| .action-btn.remove { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: #ef4444; | |
| } | |
| .action-btn:hover { | |
| transform: scale(1.1); | |
| } | |
| /* Toast Notifications */ | |
| .toast-container { | |
| position: fixed; | |
| top: 1rem; | |
| left: 1rem; | |
| z-index: 10001; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .toast { | |
| background: var(--card-bg); | |
| border-radius: var(--border-radius-sm); | |
| padding: 1rem 1.5rem; | |
| box-shadow: var(--shadow-lg); | |
| border-left: 4px solid; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.8rem; | |
| min-width: 300px; | |
| transform: translateX(-100%); | |
| transition: all 0.3s ease; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| .toast.success { border-left-color: #10b981; } | |
| .toast.error { border-left-color: #ef4444; } | |
| .toast.warning { border-left-color: #f59e0b; } | |
| .toast.info { border-left-color: #3b82f6; } | |
| .toast-icon { | |
| font-size: 1.2rem; | |
| } | |
| .toast.success .toast-icon { color: #10b981; } | |
| .toast.error .toast-icon { color: #ef4444; } | |
| .toast.warning .toast-icon { color: #f59e0b; } | |
| .toast.info .toast-icon { color: #3b82f6; } | |
| .toast-content { | |
| flex: 1; | |
| } | |
| .toast-title { | |
| font-weight: 600; | |
| font-size: var(--font-size-sm); | |
| margin-bottom: 0.2rem; | |
| } | |
| .toast-message { | |
| font-size: var(--font-size-xs); | |
| color: var(--text-secondary); | |
| } | |
| .toast-close { | |
| background: none; | |
| border: none; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| font-size: 1rem; | |
| transition: var(--transition-fast); | |
| } | |
| .toast-close:hover { | |
| color: var(--text-primary); | |
| } | |
| /* واکنشگرایی */ | |
| @media (max-width: 992px) { | |
| .sidebar { | |
| transform: translateX(100%); | |
| transition: transform 0.3s ease; | |
| } | |
| .sidebar.open { | |
| transform: translateX(0); | |
| } | |
| .main-content { | |
| margin-right: 0; | |
| width: 100%; | |
| padding: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="dashboard-container"> | |
| <!-- سایدبار --> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="sidebar-header"> | |
| <a href="/" class="logo"> | |
| <div class="logo-icon"> | |
| <i class="fas fa-scale-balanced"></i> | |
| </div> | |
| <div class="logo-text">سامانه حقوقی</div> | |
| </a> | |
| </div> | |
| <nav> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">داشبورد</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="/" class="nav-link"> | |
| <i class="fas fa-chart-pie nav-icon"></i> | |
| <span>نمای کلی</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">مدیریت اسناد</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="/static/documents.html" class="nav-link"> | |
| <i class="fas fa-file-alt nav-icon"></i> | |
| <span>مدیریت اسناد</span> | |
| <span class="nav-badge" id="totalDocumentsBadge">6</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="/static/upload.html" class="nav-link active"> | |
| <i class="fas fa-cloud-upload-alt nav-icon"></i> | |
| <span>آپلود فایل</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="/static/search.html" class="nav-link"> | |
| <i class="fas fa-search nav-icon"></i> | |
| <span>جستجو</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">ابزارها</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="/static/scraping.html" class="nav-link"> | |
| <i class="fas fa-globe nav-icon"></i> | |
| <span>استخراج محتوا</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="/static/analytics.html" class="nav-link"> | |
| <i class="fas fa-chart-line nav-icon"></i> | |
| <span>آمار و تحلیل</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="/static/reports.html" class="nav-link"> | |
| <i class="fas fa-file-export nav-icon"></i> | |
| <span>گزارشها</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">تنظیمات</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="/static/settings.html" class="nav-link"> | |
| <i class="fas fa-cog nav-icon"></i> | |
| <span>تنظیمات</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="#" class="nav-link"> | |
| <i class="fas fa-sign-out-alt nav-icon"></i> | |
| <span>خروج</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| </nav> | |
| </aside> | |
| <!-- محتوای اصلی --> | |
| <main class="main-content"> | |
| <!-- هدر صفحه --> | |
| <header class="page-header"> | |
| <h1 class="page-title"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| آپلود فایلهای حقوقی | |
| </h1> | |
| <div class="page-actions"> | |
| <a href="/static/documents.html" class="btn btn-outline"> | |
| <i class="fas fa-list"></i> | |
| مشاهده اسناد | |
| </a> | |
| </div> | |
| </header> | |
| <!-- بخش آپلود اصلی --> | |
| <section class="upload-section"> | |
| <div class="upload-area" id="uploadArea"> | |
| <div class="upload-icon"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| </div> | |
| <h2 class="upload-title">آپلود اسناد PDF</h2> | |
| <p class="upload-description"> | |
| فایلهای PDF خود را بکشید و اینجا رها کنید یا کلیک کنید تا انتخاب کنید.<br> | |
| چندین فایل را میتوانید همزمان آپلود کنید. | |
| </p> | |
| <input type="file" class="upload-input" id="fileInput" multiple accept=".pdf"> | |
| <button type="button" class="upload-btn" onclick="document.getElementById('fileInput').click()"> | |
| <i class="fas fa-folder-open"></i> | |
| انتخاب فایلها | |
| </button> | |
| </div> | |
| </section> | |
| <!-- فایلهای آپلود شده --> | |
| <section class="upload-files" id="uploadFiles"> | |
| <div class="files-header"> | |
| <h3 class="files-title">فایلهای آپلود شده</h3> | |
| <div class="files-actions"> | |
| <button type="button" class="btn btn-success btn-sm" onclick="processAllFiles()"> | |
| <i class="fas fa-play"></i> | |
| پردازش همه | |
| </button> | |
| <button type="button" class="btn btn-danger btn-sm" onclick="clearAllFiles()"> | |
| <i class="fas fa-trash"></i> | |
| پاک کردن همه | |
| </button> | |
| </div> | |
| </div> | |
| <div id="filesContainer"> | |
| <!-- فایلها اینجا نمایش داده میشوند --> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <!-- Toast Container --> | |
| <div class="toast-container" id="toastContainer"></div> | |
| <script> | |
| // Global variables | |
| let uploadedFiles = new Map(); | |
| let fileCounter = 0; | |
| let isOnline = false; | |
| // Initialize page | |
| document.addEventListener('DOMContentLoaded', function() { | |
| console.log('📤 Upload page loading...'); | |
| initializeUploadPage(); | |
| }); | |
| async function initializeUploadPage() { | |
| try { | |
| // Test backend connection | |
| isOnline = await testConnection(); | |
| // Setup event listeners | |
| setupEventListeners(); | |
| showToast('صفحه آپلود آماده است', 'success', 'آماده'); | |
| } catch (error) { | |
| console.error('Failed to initialize upload page:', error); | |
| // Fallback mode | |
| isOnline = false; | |
| setupEventListeners(); | |
| showToast('حالت آفلاین فعال است', 'warning', 'اتصال ناموفق'); | |
| } | |
| } | |
| async function testConnection() { | |
| try { | |
| await window.legalAPI.healthCheck(); | |
| return true; | |
| } catch (error) { | |
| return false; | |
| } | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| const uploadArea = document.getElementById('uploadArea'); | |
| const fileInput = document.getElementById('fileInput'); | |
| // Drag and drop events | |
| uploadArea.addEventListener('dragover', handleDragOver); | |
| uploadArea.addEventListener('dragleave', handleDragLeave); | |
| uploadArea.addEventListener('drop', handleDrop); | |
| // File input change | |
| fileInput.addEventListener('change', handleFileSelect); | |
| // Click to upload | |
| uploadArea.addEventListener('click', (e) => { | |
| if (!e.target.closest('.upload-btn') && !e.target.classList.contains('upload-input')) { | |
| fileInput.click(); | |
| } | |
| }); | |
| } | |
| // Drag and drop handlers | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| e.currentTarget.classList.add('dragover'); | |
| } | |
| function handleDragLeave(e) { | |
| e.currentTarget.classList.remove('dragover'); | |
| } | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| e.currentTarget.classList.remove('dragover'); | |
| const files = Array.from(e.dataTransfer.files).filter(file => | |
| file.type === 'application/pdf' | |
| ); | |
| if (files.length > 0) { | |
| handleFileUpload(files); | |
| } else { | |
| showToast('فقط فایلهای PDF پذیرفته میشوند', 'warning', 'هشدار'); | |
| } | |
| } | |
| // File selection handler | |
| function handleFileSelect(e) { | |
| const files = Array.from(e.target.files); | |
| if (files.length > 0) { | |
| handleFileUpload(files); | |
| } | |
| } | |
| // File upload processing | |
| async function handleFileUpload(files) { | |
| const uploadFilesSection = document.getElementById('uploadFiles'); | |
| const filesContainer = document.getElementById('filesContainer'); | |
| uploadFilesSection.classList.add('has-files'); | |
| let validFiles = []; | |
| let invalidFiles = 0; | |
| for (const file of files) { | |
| // Validate file | |
| const validation = validateFile(file); | |
| if (!validation.valid) { | |
| showToast(`${file.name}: ${validation.error}`, 'error', 'خطای اعتبارسنجی'); | |
| invalidFiles++; | |
| continue; | |
| } | |
| validFiles.push(file); | |
| const fileId = generateFileId(); | |
| const fileItem = createFileItem(file, fileId); | |
| filesContainer.appendChild(fileItem); | |
| // Store file info | |
| uploadedFiles.set(fileId, { | |
| file: file, | |
| status: 'ready', | |
| progress: 0 | |
| }); | |
| } | |
| if (validFiles.length > 0) { | |
| showToast(`${validFiles.length} فایل آماده پردازش شد`, 'success', 'آپلود موفق'); | |
| // Start upload to backend if online | |
| if (isOnline) { | |
| await uploadFilesToBackend(validFiles); | |
| } else { | |
| // Simulate upload in offline mode | |
| validFiles.forEach((file, index) => { | |
| const fileId = Array.from(uploadedFiles.keys())[uploadedFiles.size - validFiles.length + index]; | |
| setTimeout(() => { | |
| simulateFileUpload(fileId); | |
| }, Math.random() * 1000); | |
| }); | |
| } | |
| } | |
| if (invalidFiles > 0) { | |
| showToast(`${invalidFiles} فایل نامعتبر حذف شد`, 'warning', 'هشدار'); | |
| } | |
| // Clear file input | |
| document.getElementById('fileInput').value = ''; | |
| } | |
| // Upload files to backend | |
| async function uploadFilesToBackend(files) { | |
| try { | |
| const response = await window.legalAPI.uploadFiles(files); | |
| if (response.results) { | |
| response.results.forEach((result, index) => { | |
| const fileId = Array.from(uploadedFiles.keys())[uploadedFiles.size - files.length + index]; | |
| const fileData = uploadedFiles.get(fileId); | |
| if (result.success) { | |
| // Real upload successful - monitor progress | |
| monitorUploadProgress(fileId, result.document_id); | |
| } else { | |
| // Upload failed | |
| updateFileStatus(fileId, 'error', result.error || 'خطا در آپلود'); | |
| showFileRetryOption(fileId); | |
| } | |
| }); | |
| } | |
| showToast(`${response.successful_uploads} فایل با موفقیت آپلود شد`, 'success', 'آپلود موفق'); | |
| } catch (error) { | |
| console.error('Upload to backend failed:', error); | |
| showToast('خطا در ارسال فایلها به سرور', 'error', 'خطای آپلود'); | |
| // Fallback to simulation | |
| files.forEach((file, index) => { | |
| const fileId = Array.from(uploadedFiles.keys())[uploadedFiles.size - files.length + index]; | |
| setTimeout(() => { | |
| simulateFileUpload(fileId); | |
| }, Math.random() * 1000); | |
| }); | |
| } | |
| } | |
| // Monitor upload progress for real backend uploads | |
| async function monitorUploadProgress(fileId, documentId) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| updateFileStatus(fileId, 'uploading', 'در حال آپلود...'); | |
| // Simulate upload progress | |
| let progress = 0; | |
| const uploadInterval = setInterval(() => { | |
| progress += Math.random() * 15; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(uploadInterval); | |
| // Start monitoring OCR processing | |
| setTimeout(() => { | |
| monitorProcessingStatus(fileId, documentId); | |
| }, 500); | |
| } | |
| const progressBar = document.querySelector(`#file-${fileId} .file-progress-bar`); | |
| if (progressBar) { | |
| progressBar.style.width = `${progress}%`; | |
| } | |
| }, 200); | |
| } | |
| // Monitor processing status from backend | |
| async function monitorProcessingStatus(fileId, documentId) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| updateFileStatus(fileId, 'processing', 'در حال پردازش OCR...'); | |
| // Poll for processing status | |
| const pollInterval = setInterval(async () => { | |
| try { | |
| const document = await window.legalAPI.getDocument(documentId); | |
| if (document.status === 'processed') { | |
| clearInterval(pollInterval); | |
| updateFileStatus(fileId, 'success', 'پردازش تکمیل شد'); | |
| showToast(`${fileData.file.name} با موفقیت پردازش شد`, 'success', 'پردازش موفق'); | |
| } else if (document.status === 'error') { | |
| clearInterval(pollInterval); | |
| updateFileStatus(fileId, 'error', 'خطا در پردازش'); | |
| showFileRetryOption(fileId); | |
| showToast(`خطا در پردازش ${fileData.file.name}`, 'error', 'خطای پردازش'); | |
| } | |
| // Continue polling for other statuses (processing, pending) | |
| } catch (error) { | |
| console.error('Failed to check processing status:', error); | |
| // Don't clear interval yet, might be temporary network issue | |
| } | |
| }, 3000); // Poll every 3 seconds | |
| // Stop polling after 5 minutes | |
| setTimeout(() => { | |
| clearInterval(pollInterval); | |
| if (fileData.status !== 'success' && fileData.status !== 'error') { | |
| updateFileStatus(fileId, 'processing', 'پردازش ادامه دارد...'); | |
| } | |
| }, 300000); | |
| } | |
| // Validate file | |
| function validateFile(file) { | |
| if (file.type !== 'application/pdf') { | |
| return { valid: false, error: 'فقط فایلهای PDF پذیرفته میشوند' }; | |
| } | |
| if (file.size > 50 * 1024 * 1024) { // 50MB | |
| return { valid: false, error: 'حجم فایل نباید بیشتر از 50 مگابایت باشد' }; | |
| } | |
| if (file.size < 1024) { // 1KB | |
| return { valid: false, error: 'فایل خیلی کوچک است' }; | |
| } | |
| return { valid: true }; | |
| } | |
| // Create file item UI | |
| function createFileItem(file, fileId) { | |
| const fileItem = document.createElement('div'); | |
| fileItem.className = 'file-item'; | |
| fileItem.id = `file-${fileId}`; | |
| fileItem.innerHTML = ` | |
| <div class="file-icon"> | |
| <i class="fas fa-file-pdf"></i> | |
| </div> | |
| <div class="file-info"> | |
| <div class="file-name">${file.name}</div> | |
| <div class="file-details"> | |
| <div class="file-size"> | |
| <i class="fas fa-weight-hanging"></i> | |
| ${formatFileSize(file.size)} | |
| </div> | |
| <div class="file-type"> | |
| <i class="fas fa-file-pdf"></i> | |
| PDF Document | |
| </div> | |
| </div> | |
| <div class="file-progress"> | |
| <div class="file-progress-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="file-status uploading"> | |
| <i class="fas fa-clock"></i> | |
| آماده | |
| </div> | |
| <div class="file-actions"> | |
| <button type="button" class="action-btn retry" onclick="retryUpload('${fileId}')" title="تلاش مجدد" style="display: none;"> | |
| <i class="fas fa-redo"></i> | |
| </button> | |
| <button type="button" class="action-btn remove" onclick="removeFile('${fileId}')" title="حذف"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| `; | |
| return fileItem; | |
| } | |
| // Simulate file upload and processing (offline mode) | |
| function simulateFileUpload(fileId) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| const fileItem = document.getElementById(`file-${fileId}`); | |
| const statusElement = fileItem.querySelector('.file-status'); | |
| const progressBar = fileItem.querySelector('.file-progress-bar'); | |
| // Phase 1: Uploading | |
| updateFileStatus(fileId, 'uploading', 'در حال آپلود...'); | |
| let progress = 0; | |
| const uploadInterval = setInterval(() => { | |
| progress += Math.random() * 15; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(uploadInterval); | |
| // Phase 2: Processing | |
| setTimeout(() => { | |
| simulateProcessing(fileId); | |
| }, 500); | |
| } | |
| progressBar.style.width = `${progress}%`; | |
| }, 200); | |
| } | |
| // Simulate OCR processing (offline mode) | |
| function simulateProcessing(fileId) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| updateFileStatus(fileId, 'processing', 'در حال پردازش OCR...'); | |
| // Simulate processing time based on file size | |
| const fileSize = fileData.file.size; | |
| const processingTime = Math.min(fileSize / (1024 * 1024) * 1000 + 2000, 8000); // 2-8 seconds | |
| setTimeout(() => { | |
| // Random success/failure (90% success rate) | |
| const success = Math.random() > 0.1; | |
| if (success) { | |
| updateFileStatus(fileId, 'success', 'پردازش تکمیل شد'); | |
| showToast(`${fileData.file.name} با موفقیت پردازش شد`, 'success', 'پردازش موفق'); | |
| } else { | |
| updateFileStatus(fileId, 'error', 'خطا در پردازش'); | |
| showFileRetryOption(fileId); | |
| showToast(`خطا در پردازش ${fileData.file.name}`, 'error', 'خطای پردازش'); | |
| } | |
| }, processingTime); | |
| } | |
| // Update file status | |
| function updateFileStatus(fileId, status, message) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| fileData.status = status; | |
| const fileItem = document.getElementById(`file-${fileId}`); | |
| if (!fileItem) return; | |
| const statusElement = fileItem.querySelector('.file-status'); | |
| const progressBar = fileItem.querySelector('.file-progress-bar'); | |
| statusElement.className = `file-status ${status}`; | |
| const icons = { | |
| ready: 'clock', | |
| uploading: 'spinner fa-spin', | |
| processing: 'cog fa-spin', | |
| success: 'check-circle', | |
| error: 'exclamation-triangle' | |
| }; | |
| statusElement.innerHTML = ` | |
| <i class="fas fa-${icons[status]}"></i> | |
| ${message} | |
| `; | |
| switch (status) { | |
| case 'success': | |
| progressBar.style.width = '100%'; | |
| progressBar.style.background = 'var(--success-gradient)'; | |
| break; | |
| case 'error': | |
| progressBar.style.width = '100%'; | |
| progressBar.style.background = 'var(--danger-gradient)'; | |
| break; | |
| } | |
| } | |
| // Show retry option for failed files | |
| function showFileRetryOption(fileId) { | |
| const fileItem = document.getElementById(`file-${fileId}`); | |
| if (!fileItem) return; | |
| const retryBtn = fileItem.querySelector('.action-btn.retry'); | |
| retryBtn.style.display = 'flex'; | |
| } | |
| // Retry file upload | |
| async function retryUpload(fileId) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| const fileItem = document.getElementById(`file-${fileId}`); | |
| const retryBtn = fileItem.querySelector('.action-btn.retry'); | |
| retryBtn.style.display = 'none'; | |
| fileData.status = 'ready'; | |
| // Reset progress bar | |
| const progressBar = fileItem.querySelector('.file-progress-bar'); | |
| progressBar.style.width = '0%'; | |
| progressBar.style.background = 'var(--success-gradient)'; | |
| if (isOnline) { | |
| // Retry with real backend | |
| await uploadFilesToBackend([fileData.file]); | |
| } else { | |
| // Retry with simulation | |
| setTimeout(() => { | |
| simulateFileUpload(fileId); | |
| }, 500); | |
| } | |
| } | |
| // Remove file | |
| function removeFile(fileId) { | |
| const fileData = uploadedFiles.get(fileId); | |
| if (!fileData) return; | |
| if (!confirm(`آیا از حذف "${fileData.file.name}" اطمینان دارید؟`)) { | |
| return; | |
| } | |
| const fileItem = document.getElementById(`file-${fileId}`); | |
| if (fileItem) { | |
| fileItem.style.transform = 'translateX(100%)'; | |
| fileItem.style.opacity = '0'; | |
| setTimeout(() => { | |
| fileItem.remove(); | |
| uploadedFiles.delete(fileId); | |
| // Hide section if no files | |
| if (uploadedFiles.size === 0) { | |
| document.getElementById('uploadFiles').classList.remove('has-files'); | |
| } | |
| }, 300); | |
| } | |
| showToast(`${fileData.file.name} حذف شد`, 'info', 'حذف فایل'); | |
| } | |
| // Process all files | |
| async function processAllFiles() { | |
| let readyFiles = []; | |
| uploadedFiles.forEach((fileData, fileId) => { | |
| if (fileData.status === 'ready') { | |
| readyFiles.push({fileId, file: fileData.file}); | |
| } | |
| }); | |
| if (readyFiles.length === 0) { | |
| showToast('هیچ فایل آمادهای برای پردازش وجود ندارد', 'warning', 'هشدار'); | |
| return; | |
| } | |
| if (isOnline) { | |
| // Process with real backend | |
| const files = readyFiles.map(item => item.file); | |
| await uploadFilesToBackend(files); | |
| } else { | |
| // Process with simulation | |
| readyFiles.forEach(({fileId}) => { | |
| setTimeout(() => { | |
| simulateFileUpload(fileId); | |
| }, Math.random() * 1000); | |
| }); | |
| } | |
| showToast(`پردازش ${readyFiles.length} فایل شروع شد`, 'info', 'شروع پردازش'); | |
| } | |
| // Clear all files | |
| function clearAllFiles() { | |
| if (uploadedFiles.size === 0) { | |
| showToast('هیچ فایلی برای حذف وجود ندارد', 'warning', 'هشدار'); | |
| return; | |
| } | |
| if (!confirm('آیا از حذف همه فایلها اطمینان دارید؟')) { | |
| return; | |
| } | |
| const filesContainer = document.getElementById('filesContainer'); | |
| filesContainer.innerHTML = ''; | |
| uploadedFiles.clear(); | |
| document.getElementById('uploadFiles').classList.remove('has-files'); | |
| showToast('همه فایلها حذف شدند', 'info', 'پاک کردن'); | |
| } | |
| // Utility functions | |
| function generateFileId() { | |
| return `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; | |
| } | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 بایت'; | |
| const k = 1024; | |
| const sizes = ['بایت', 'کیلوبایت', 'مگابایت', 'گیگابایت']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| console.log('📤 Upload Page Ready!'); | |
| </script> | |
| </body> | |
| </html> |