Spaces:
Running
Running
| <html lang="en" data-bs-theme="dark"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>{% block title %}DocuPDF{% endblock %}</title> | |
| {% block styles %}{% endblock %} | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.bootstrap5.min.css" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> | |
| <style> | |
| /* === UNIFIED DESIGN SYSTEM === */ | |
| :root { | |
| --bg-dark: #212529; | |
| --bg-card: #2b3035; | |
| --bg-elevated: #343a40; | |
| --bg-hover: #3d444b; | |
| --border-subtle: #495057; | |
| --border-muted: #6c757d; | |
| --text-primary: #e9ecef; | |
| --text-muted: #adb5bd; | |
| --accent-primary: #0d6efd; | |
| --accent-info: #0dcaf0; | |
| --accent-success: #198754; | |
| --accent-warning: #ffc107; | |
| --accent-danger: #dc3545; | |
| --transition-fast: 0.15s ease; | |
| --transition-normal: 0.25s ease; | |
| --shadow-sm: 0 2px 4px rgba(0,0,0,0.3); | |
| --shadow-md: 0 4px 12px rgba(0,0,0,0.4); | |
| } | |
| /* --- Unified Button Pill Style --- */ | |
| .btn-pill { | |
| border-radius: 50px; | |
| font-weight: 500; | |
| transition: all var(--transition-fast); | |
| } | |
| .btn-pill:hover { | |
| transform: translateY(-1px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .btn-pill:active { | |
| transform: translateY(0); | |
| } | |
| /* --- Enhanced Button Hover Effects --- */ | |
| .btn { | |
| transition: all var(--transition-fast); | |
| } | |
| .btn:hover { | |
| transform: translateY(-1px); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| /* --- Unified Modal Headers --- */ | |
| .modal-content { | |
| border: 1px solid var(--border-subtle); | |
| } | |
| .modal-header { | |
| background: linear-gradient(180deg, var(--bg-card), var(--bg-dark)); | |
| border-bottom-color: var(--border-subtle); | |
| } | |
| .modal-footer { | |
| border-top-color: var(--border-subtle); | |
| } | |
| /* --- Unified Card Styles --- */ | |
| .card { | |
| transition: all var(--transition-fast); | |
| } | |
| .card:hover { | |
| border-color: var(--accent-primary) ; | |
| } | |
| /* --- Unified Note Section/Card --- */ | |
| .note-section, .note-card { | |
| background: linear-gradient(135deg, rgba(13, 202, 240, 0.08), rgba(13, 202, 240, 0.15)); | |
| border: 1px solid rgba(13, 202, 240, 0.3); | |
| border-radius: 10px; | |
| padding: 12px; | |
| transition: all var(--transition-fast); | |
| } | |
| .note-section:hover, .note-card:hover { | |
| border-color: var(--accent-info); | |
| box-shadow: 0 0 12px rgba(13, 202, 240, 0.15); | |
| } | |
| .note-section h6 { | |
| color: var(--accent-info); | |
| margin-bottom: 10px; | |
| font-weight: 600; | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| /* --- Unified Progress Bars --- */ | |
| .progress-bar { | |
| background: linear-gradient(90deg, var(--accent-primary), var(--accent-info)); | |
| } | |
| /* --- Tom Select Dark Mode Fix --- */ | |
| .ts-control { | |
| background-color: var(--bg-card) ; | |
| border-color: var(--border-subtle) ; | |
| color: #fff ; | |
| } | |
| .ts-control input { | |
| color: #fff ; | |
| } | |
| .ts-dropdown, .ts-dropdown .optgroup-header { | |
| background-color: var(--bg-card) ; | |
| border-color: var(--border-subtle) ; | |
| } | |
| .ts-dropdown .option:hover, .ts-dropdown .active { | |
| background-color: var(--accent-primary) ; | |
| } | |
| .ts-control .item { | |
| background-color: var(--accent-primary) ; | |
| color: #fff ; | |
| } | |
| /* --- Unified Badge Styles --- */ | |
| .badge { | |
| font-weight: 500; | |
| padding: 0.5em 0.75em; | |
| transition: all var(--transition-fast); | |
| } | |
| /* --- Unified Table Styles --- */ | |
| .table { | |
| --bs-table-bg: transparent; | |
| --bs-table-striped-bg: rgba(255, 255, 255, 0.03); | |
| --bs-table-hover-bg: rgba(13, 110, 253, 0.1); | |
| color: var(--text-primary); | |
| border-color: var(--bg-elevated); | |
| } | |
| .table th { | |
| background-color: var(--bg-dark); | |
| color: var(--text-muted); | |
| font-weight: 600; | |
| } | |
| .table td { | |
| border-color: var(--bg-elevated); | |
| vertical-align: middle; | |
| } | |
| /* --- Unified Nav Pills --- */ | |
| .nav-pills .nav-link { | |
| color: var(--text-muted); | |
| background-color: transparent; | |
| border: 1px solid var(--border-subtle); | |
| border-radius: 50px; | |
| transition: all var(--transition-fast); | |
| } | |
| .nav-pills .nav-link:hover { | |
| color: #fff; | |
| background-color: var(--bg-elevated); | |
| transform: translateY(-1px); | |
| } | |
| .nav-pills .nav-link.active { | |
| color: #fff ; | |
| background-color: var(--accent-primary) ; | |
| border-color: var(--accent-primary); | |
| } | |
| /* --- Unified Form Controls --- */ | |
| .form-control, .form-select { | |
| background-color: var(--bg-dark); | |
| border-color: var(--border-subtle); | |
| color: var(--text-primary); | |
| transition: all var(--transition-fast); | |
| } | |
| .form-control:focus, .form-select:focus { | |
| background-color: var(--bg-dark); | |
| border-color: var(--accent-primary); | |
| box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); | |
| color: var(--text-primary); | |
| } | |
| /* --- Smooth Page Transitions --- */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(8px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .container, .container-fluid { | |
| animation: fadeIn 0.25s ease-out; | |
| } | |
| /* --- Alert Animations --- */ | |
| .alert { | |
| animation: slideInDown 0.25s ease-out; | |
| } | |
| @keyframes slideInDown { | |
| from { opacity: 0; transform: translateY(-8px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* --- Dropdown Animations --- */ | |
| .dropdown-menu { | |
| animation: dropdownFadeIn 0.15s ease-out; | |
| } | |
| @keyframes dropdownFadeIn { | |
| from { opacity: 0; transform: scale(0.95); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| /* --- Smooth List Item Transitions --- */ | |
| .list-group-item { | |
| transition: all var(--transition-fast); | |
| } | |
| .list-group-item:hover { | |
| background-color: var(--bg-hover); | |
| } | |
| /* --- Focus Ring Enhancement --- */ | |
| :focus-visible { | |
| outline: 2px solid var(--accent-primary); | |
| outline-offset: 2px; | |
| } | |
| /* --- Skeleton Loading Animation --- */ | |
| .skeleton { | |
| background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--bg-hover) 50%, var(--bg-elevated) 75%); | |
| background-size: 200% 100%; | |
| animation: skeleton-loading 1.5s infinite; | |
| } | |
| @keyframes skeleton-loading { | |
| 0% { background-position: 200% 0; } | |
| 100% { background-position: -200% 0; } | |
| } | |
| #startup-sync-overlay { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 2000; | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 24px; | |
| background: | |
| radial-gradient(circle at top, rgba(13, 202, 240, 0.14), transparent 35%), | |
| rgba(12, 16, 22, 0.92); | |
| backdrop-filter: blur(10px); | |
| } | |
| #startup-sync-overlay.visible { | |
| display: flex; | |
| } | |
| .startup-sync-card { | |
| width: min(560px, 100%); | |
| padding: 28px; | |
| border-radius: 18px; | |
| border: 1px solid rgba(13, 202, 240, 0.22); | |
| background: linear-gradient(180deg, rgba(43, 48, 53, 0.96), rgba(24, 28, 32, 0.98)); | |
| box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45); | |
| } | |
| .startup-sync-progress { | |
| height: 14px; | |
| background: rgba(255, 255, 255, 0.08); | |
| border-radius: 999px; | |
| overflow: hidden; | |
| } | |
| .startup-sync-progress .progress-bar { | |
| transition: width 0.35s ease; | |
| } | |
| </style> | |
| {% block head %}{% endblock %} | |
| </head> | |
| <body> | |
| {% include '_navbar.html' %} | |
| <div class="content-wrapper" style="min-height: calc(100vh - 56px); overflow-y: auto;"> | |
| {% block content %}{% endblock %} | |
| </div> | |
| <div id="startup-sync-overlay" aria-live="polite" aria-busy="true"> | |
| <div class="startup-sync-card text-white"> | |
| <div class="d-flex align-items-center justify-content-between gap-3 mb-3"> | |
| <div> | |
| <div class="text-uppercase text-info small fw-semibold mb-1">Startup Sync</div> | |
| <h4 class="mb-0">Loading Hugging Face dataset</h4> | |
| </div> | |
| <i class="bi bi-cloud-arrow-down fs-2 text-info"></i> | |
| </div> | |
| <p id="startup-sync-message" class="text-light mb-2">Preparing initial data sync...</p> | |
| <p id="startup-sync-detail" class="text-secondary small mb-3"></p> | |
| <div class="startup-sync-progress mb-2"> | |
| <div id="startup-sync-bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div> | |
| </div> | |
| <div class="d-flex justify-content-between align-items-center"> | |
| <span id="startup-sync-percent" class="small text-info">0%</span> | |
| <span id="startup-sync-state" class="small text-secondary">Waiting for sync status...</span> | |
| </div> | |
| <div id="startup-sync-error" class="alert alert-danger mt-3 mb-0 d-none"></div> | |
| </div> | |
| </div> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script> | |
| <script> | |
| (function() { | |
| const overlay = document.getElementById('startup-sync-overlay'); | |
| const message = document.getElementById('startup-sync-message'); | |
| const detail = document.getElementById('startup-sync-detail'); | |
| const bar = document.getElementById('startup-sync-bar'); | |
| const percent = document.getElementById('startup-sync-percent'); | |
| const state = document.getElementById('startup-sync-state'); | |
| const errorBox = document.getElementById('startup-sync-error'); | |
| let wasVisible = false; | |
| function setOverlayVisible(visible) { | |
| overlay.classList.toggle('visible', visible); | |
| wasVisible = wasVisible || visible; | |
| } | |
| async function pollStartupSync() { | |
| try { | |
| const response = await fetch('/hf-sync/status', { cache: 'no-store' }); | |
| if (!response.ok) { | |
| return; | |
| } | |
| const data = await response.json(); | |
| const isStartupDownload = data.action === 'download' && (data.status === 'running' || data.status === 'failed'); | |
| if (!isStartupDownload) { | |
| setOverlayVisible(false); | |
| if (wasVisible && data.status === 'completed') { | |
| window.location.reload(); | |
| } | |
| return; | |
| } | |
| setOverlayVisible(true); | |
| message.textContent = data.message || 'Syncing startup data...'; | |
| detail.textContent = data.detail || ''; | |
| state.textContent = data.status === 'failed' ? 'Sync failed' : 'Sync in progress'; | |
| percent.textContent = `${data.progress || 0}%`; | |
| bar.style.width = `${data.progress || 0}%`; | |
| if (data.status === 'failed') { | |
| bar.classList.remove('progress-bar-animated'); | |
| bar.classList.add('bg-danger'); | |
| errorBox.classList.remove('d-none'); | |
| errorBox.textContent = data.error || 'The initial Hugging Face sync failed.'; | |
| } else { | |
| bar.classList.add('progress-bar-animated'); | |
| bar.classList.remove('bg-danger'); | |
| errorBox.classList.add('d-none'); | |
| } | |
| } catch (error) { | |
| if (wasVisible) { | |
| errorBox.classList.remove('d-none'); | |
| errorBox.textContent = `Unable to read sync status: ${error}`; | |
| } | |
| } | |
| } | |
| pollStartupSync(); | |
| window.setInterval(pollStartupSync, 1500); | |
| })(); | |
| </script> | |
| {% block scripts %}{% endblock %} | |
| </body> | |
| </html> | |