Report-Generator / templates /upload_final_pdf.html
Jaimodiji's picture
Upload folder using huggingface_hub
c001f24
{% extends "base.html" %}
{% block title %}Upload Final PDF{% endblock %}
{% block head %}
<!-- Ensure mobile viewport settings are correct -->
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1, viewport-fit=cover">
<style>
:root {
--app-bg: #181a1c;
--card-bg: #212529;
--input-bg: #2c3034;
--border-color: #495057;
--primary-color: #0d6efd;
--touch-target: 48px;
}
body {
background-color: var(--app-bg);
color: #e9ecef;
}
/* Full height container with dynamic viewport support */
.upload-wrapper {
min-height: calc(100vh - 56px);
min-height: calc(100dvh - 56px);
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
padding-bottom: env(safe-area-inset-bottom);
}
.main-card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
width: 100%;
max-width: 600px;
overflow: hidden;
}
/* Custom segmented tabs */
.nav-pills {
background: var(--input-bg);
padding: 4px;
border-radius: 12px;
gap: 4px;
}
.nav-pills .nav-link {
border-radius: 8px;
color: #adb5bd !important;
font-size: 0.9rem;
padding: 10px;
transition: all 0.2s;
}
.nav-pills .nav-link.active {
background-color: var(--primary-color) !important;
color: white !important;
font-weight: 600;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
/* File Drop Zone Styling */
.file-drop-area {
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
max-width: 100%;
padding: 30px;
border: 2px dashed var(--border-color);
border-radius: 12px;
transition: 0.2s;
background: rgba(255,255,255,0.02);
cursor: pointer;
}
.file-drop-area:hover, .file-drop-area.drag-over {
border-color: var(--primary-color);
background: rgba(13, 110, 253, 0.05);
}
.fake-btn {
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 8px 16px;
font-size: 0.9rem;
margin-top: 10px;
}
.file-msg {
font-size: 0.95rem;
font-weight: 500;
color: #dee2e6;
text-align: center;
}
.file-input {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
opacity: 0;
cursor: pointer;
}
/* Large touch-friendly inputs */
.form-control-lg {
background-color: var(--input-bg);
border-color: var(--border-color);
color: white;
font-size: 16px; /* Prevents iOS zoom */
min-height: var(--touch-target);
}
.form-control-lg:focus {
background-color: var(--input-bg);
border-color: var(--primary-color);
color: white;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
}
/* Main Action Button */
.btn-action {
min-height: 54px;
font-size: 1.1rem;
font-weight: 600;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
transition: transform 0.1s;
}
.btn-action:active {
transform: scale(0.98);
}
/* Animations */
.fade-in-up {
animation: fadeInUp 0.4s ease-out forwards;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
{% endblock %}
{% block content %}
<div class="upload-wrapper">
<div class="main-card fade-in-up">
<!-- Header -->
<div class="p-4 border-bottom border-secondary" style="border-color: #495057 !important;">
<div class="text-center">
<div class="bg-success bg-opacity-10 text-success d-inline-flex align-items-center justify-content-center rounded-circle mb-3" style="width: 56px; height: 56px;">
<i class="bi bi-file-earmark-check-fill fs-3"></i>
</div>
<h1 class="h4 mb-1">Upload Final PDF</h1>
<p class="text-secondary small mb-0">Add a finished report to your collection</p>
</div>
</div>
<div class="card-body p-4">
<form id="upload-form">
<!-- Subject Field (Common) -->
<div class="mb-4">
<label for="subject" class="form-label small text-secondary">Subject (Required)</label>
<input type="text" class="form-control form-control-lg" id="subject" name="subject" placeholder="e.g. Mathematics Test 1" required>
</div>
<!-- Navigation Tabs -->
<ul class="nav nav-pills nav-fill mb-4" id="uploadMethodTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="file-tab" data-bs-toggle="tab" data-bs-target="#file-upload-pane" type="button" role="tab" aria-controls="file-upload-pane" aria-selected="true">
<i class="bi bi-folder2-open me-1"></i> File
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="url-tab" data-bs-toggle="tab" data-bs-target="#url-upload-pane" type="button" role="tab" aria-controls="url-upload-pane" aria-selected="false">
<i class="bi bi-link-45deg me-1"></i> URL
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="curl-tab" data-bs-toggle="tab" data-bs-target="#curl-upload-pane" type="button" role="tab" aria-controls="curl-upload-pane" aria-selected="false">
<i class="bi bi-code-slash me-1"></i> cURL
</button>
</li>
</ul>
<div class="tab-content mb-4">
<!-- File Upload Pane -->
<div class="tab-pane fade show active" id="file-upload-pane" role="tabpanel" aria-labelledby="file-tab">
<div class="file-drop-area" id="drop-zone">
<span class="file-msg" id="file-label-text">Tap to select or drag PDF here</span>
<span class="fake-btn">Choose File</span>
<input class="file-input" type="file" id="pdf-upload" name="pdf" accept=".pdf">
</div>
</div>
<!-- URL Pane -->
<div class="tab-pane fade" id="url-upload-pane" role="tabpanel" aria-labelledby="url-tab">
<div class="mb-3">
<label for="pdf-url" class="form-label small text-secondary">PDF URL Address</label>
<input type="url" class="form-control form-control-lg" id="pdf-url" name="pdf_url" placeholder="https://example.com/doc.pdf or Google Drive Link" autocomplete="off">
<div class="form-text text-muted small mt-1"><i class="bi bi-google"></i> Google Drive sharing links supported</div>
</div>
</div>
<!-- cURL Pane -->
<div class="tab-pane fade" id="curl-upload-pane" role="tabpanel" aria-labelledby="curl-tab">
<div class="mb-3">
<label for="curl-command" class="form-label small text-secondary">Paste cURL Command</label>
<textarea class="form-control form-control-lg" id="curl-command" name="curl_command" placeholder="curl -o 'report.pdf' ..." rows="2" style="font-family: monospace; font-size: 14px;"></textarea>
</div>
</div>
</div>
<!-- Main Action Button -->
<button type="submit" class="btn btn-primary w-100 btn-action shadow">
<span class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<span class="btn-text">Upload PDF</span>
<i class="bi bi-arrow-right-circle btn-icon"></i>
</button>
</form>
<div id="status" class="mt-3"></div>
<div class="mt-4 text-center">
<a href="/pdf_manager" class="text-secondary text-decoration-none small">
<i class="bi bi-arrow-left"></i> Back to PDF Manager
</a>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// --- File Input UX ---
const fileInput = document.getElementById('pdf-upload');
const dropZone = document.getElementById('drop-zone');
const fileLabel = document.getElementById('file-label-text');
// Visual feedback for file selection
fileInput.addEventListener('change', function() {
if (this.files && this.files.length > 0) {
fileLabel.innerHTML = `<i class="bi bi-check-circle-fill text-success"></i> ${this.files[0].name}`;
dropZone.style.borderColor = '#198754';
dropZone.style.background = 'rgba(25, 135, 84, 0.1)';
} else {
fileLabel.textContent = "Tap to select or drag PDF here";
dropZone.style.borderColor = '';
dropZone.style.background = '';
}
});
// Drag and drop visual effects
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('drag-over');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('drag-over');
}, false);
});
// --- Paste Support ---
document.addEventListener('paste', function(event) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let index in items) {
const item = items[index];
if (item.kind === 'file') {
const blob = item.getAsFile();
if (blob.type === 'application/pdf') {
// It's a PDF!
const dataTransfer = new DataTransfer();
dataTransfer.items.add(blob);
fileInput.files = dataTransfer.files;
// Trigger change event to update UI
const changeEvent = new Event('change');
fileInput.dispatchEvent(changeEvent);
// Switch to file tab if not active
const fileTabBtn = document.querySelector('#file-tab');
if (!fileTabBtn.classList.contains('active')) {
const tab = new bootstrap.Tab(fileTabBtn);
tab.show();
}
break;
}
}
}
});
// --- Tab Handling ---
const tabs = document.querySelectorAll('#uploadMethodTab .nav-link');
tabs.forEach(tab => {
tab.addEventListener('shown.bs.tab', function (event) {
const currentTabId = event.target.id;
document.getElementById('pdf-upload').required = (currentTabId === 'file-tab');
document.getElementById('pdf-url').required = (currentTabId === 'url-tab');
document.getElementById('curl-command').required = (currentTabId === 'curl-tab');
});
});
// Set initial required state
document.getElementById('pdf-upload').required = true;
// --- Form Submission ---
document.getElementById('upload-form').addEventListener('submit', async function(e) {
e.preventDefault();
const form = e.target;
const button = form.querySelector('button[type="submit"]');
const spinner = button.querySelector('.spinner-border');
const btnText = button.querySelector('.btn-text');
const btnIcon = button.querySelector('.btn-icon');
const statusDiv = document.getElementById('status');
// Reset UI
statusDiv.innerHTML = '';
button.disabled = true;
spinner.classList.remove('d-none');
btnIcon.classList.add('d-none');
btnText.textContent = "Uploading...";
const activeTab = document.querySelector('#uploadMethodTab .nav-link.active').id;
const formData = new FormData(form);
// Validation & Data Prep (Subject is required and handled by 'required' attr)
let isValid = false;
// Subject Validation (Double check)
const subject = formData.get('subject');
if (!subject || !subject.trim()) {
statusDiv.innerHTML = `<div class="alert alert-danger d-flex align-items-center"><i class="bi bi-exclamation-triangle-fill me-2"></i> Subject is required.</div>`;
button.disabled = false;
spinner.classList.add('d-none');
btnIcon.classList.remove('d-none');
btnText.textContent = "Upload PDF";
return;
}
if (activeTab === 'file-tab') {
const fileInput = document.getElementById('pdf-upload');
if (fileInput.files.length > 0) {
// formData already has 'pdf' from constructor
isValid = true;
} else {
statusDiv.innerHTML = `<div class="alert alert-danger d-flex align-items-center"><i class="bi bi-exclamation-triangle-fill me-2"></i> Please select a PDF file.</div>`;
}
} else if (activeTab === 'url-tab') {
const urlInput = document.getElementById('pdf-url');
if (urlInput.value) {
// formData has 'pdf_url'
isValid = true;
} else {
statusDiv.innerHTML = `<div class="alert alert-danger d-flex align-items-center"><i class="bi bi-exclamation-triangle-fill me-2"></i> Please enter a PDF URL.</div>`;
}
} else if (activeTab === 'curl-tab') {
const curlInput = document.getElementById('curl-command');
if (curlInput.value) {
// formData has 'curl_command'
isValid = true;
} else {
statusDiv.innerHTML = `<div class="alert alert-danger d-flex align-items-center"><i class="bi bi-exclamation-triangle-fill me-2"></i> Please enter a cURL command.</div>`;
}
}
if (!isValid) {
button.disabled = false;
spinner.classList.add('d-none');
btnIcon.classList.remove('d-none');
btnText.textContent = "Upload PDF";
return;
}
try {
const response = await fetch('/handle_final_pdf_upload', {
method: 'POST',
body: formData
});
if (response.ok) {
// Check if redirected to pdf_manager (success)
if (response.redirected) {
window.location.href = response.url;
} else {
// Fallback if not redirected but successful (e.g. 200 OK text)
// But typically this route redirects.
window.location.href = '/pdf_manager';
}
} else {
const errorText = await response.text();
throw new Error(errorText || 'Upload failed');
}
} catch (error) {
statusDiv.innerHTML = `
<div class="alert alert-danger border-0 bg-danger bg-opacity-10 text-danger">
<i class="bi bi-x-circle-fill me-2"></i> ${error.message}
</div>
`;
button.disabled = false;
button.className = "btn btn-primary w-100 btn-action shadow";
spinner.classList.add('d-none');
btnIcon.classList.remove('d-none');
btnText.textContent = "Try Again";
}
});
</script>
{% endblock %}