255 lines
8.4 KiB
HTML
255 lines
8.4 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Upload Document{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="flex flex-col items-center justify-center p-8">
|
|
<h1 class="text-3xl font-bold mb-8">Upload Files</h1>
|
|
|
|
<form action="/api/ui-upload" method="POST" enctype="multipart/form-data">
|
|
<div
|
|
id="dropZone"
|
|
class="border-4 border-dashed border-gray-300 rounded-lg p-8 bg-white text-center w-full max-w-lg"
|
|
ondrop="handleDrop(event)"
|
|
ondragover="handleDragOver(event)"
|
|
ondragleave="handleDragLeave(event)"
|
|
>
|
|
<p class="text-gray-500 mb-4">
|
|
Drag & drop files here, or click to select files.
|
|
</p>
|
|
<input
|
|
id="fileInput"
|
|
type="file"
|
|
class="hidden"
|
|
onchange="handleFileSelect(event)"
|
|
name="files"
|
|
multiple
|
|
/>
|
|
<div class="text-sm text-gray-500 mt-2">
|
|
<p>Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images</p>
|
|
<p>Maximum size: 500MB per file</p>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div id="statusMessage" class="mt-4 text-gray-700"></div>
|
|
<div id="uploadProgress" class="mt-4 w-full max-w-lg"></div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Configuration
|
|
const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB
|
|
|
|
// Allowed file types
|
|
const ACCEPTED_TYPES = {
|
|
// PDF files
|
|
'application/pdf': true,
|
|
|
|
// Image formats
|
|
'image/jpeg': true, 'image/jpg': true, 'image/png': true,
|
|
'image/gif': true, 'image/bmp': true, 'image/tiff': true,
|
|
'image/webp': true, 'image/svg+xml': true,
|
|
|
|
// Office document formats - Word
|
|
'application/msword': true,
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': true,
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.template': true,
|
|
'application/vnd.ms-word.document.macroEnabled.12': true,
|
|
|
|
// Excel
|
|
'application/vnd.ms-excel': true,
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': true,
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.template': true,
|
|
'application/vnd.ms-excel.sheet.macroEnabled.12': true,
|
|
|
|
// PowerPoint
|
|
'application/vnd.ms-powerpoint': true,
|
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation': true,
|
|
'application/vnd.openxmlformats-officedocument.presentationml.template': true,
|
|
'application/vnd.openxmlformats-officedocument.presentationml.slideshow': true,
|
|
|
|
// Other common formats
|
|
'text/plain': true,
|
|
'text/csv': true,
|
|
'application/rtf': true,
|
|
'text/rtf': true,
|
|
'text/html': true,
|
|
'application/xml': true,
|
|
'text/xml': true
|
|
};
|
|
|
|
// File extensions that are always allowed (even if mime type is not recognized)
|
|
const ACCEPTED_EXTENSIONS = [
|
|
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
'.odt', '.ods', '.odp', '.rtf', '.txt', '.csv',
|
|
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg', '.md'
|
|
];
|
|
|
|
// Drag-and-drop / file input logic
|
|
const dropZone = document.getElementById("dropZone");
|
|
const fileInput = document.getElementById("fileInput");
|
|
const statusMessage = document.getElementById("statusMessage");
|
|
const uploadProgress = document.getElementById("uploadProgress");
|
|
|
|
dropZone.addEventListener("click", () => fileInput.click());
|
|
|
|
function handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = "copy";
|
|
dropZone.classList.add("bg-gray-100");
|
|
}
|
|
|
|
function handleDragLeave(e) {
|
|
e.preventDefault();
|
|
dropZone.classList.remove("bg-gray-100");
|
|
}
|
|
|
|
function handleDrop(e) {
|
|
e.preventDefault();
|
|
dropZone.classList.remove("bg-gray-100");
|
|
if (e.dataTransfer.files.length) {
|
|
processFiles(e.dataTransfer.files);
|
|
}
|
|
}
|
|
|
|
function handleFileSelect(e) {
|
|
if (e.target.files.length) {
|
|
processFiles(e.target.files);
|
|
}
|
|
}
|
|
|
|
function processFiles(files) {
|
|
if (files.length === 0) return;
|
|
|
|
statusMessage.textContent = `Processing ${files.length} file(s)...`;
|
|
|
|
// Clear previous upload progress
|
|
uploadProgress.innerHTML = "";
|
|
|
|
// Create progress container
|
|
const progressContainer = document.createElement("div");
|
|
progressContainer.className = "space-y-2";
|
|
uploadProgress.appendChild(progressContainer);
|
|
|
|
// Process each file
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
validateAndUpload(file, progressContainer);
|
|
}
|
|
}
|
|
|
|
function validateAndUpload(file, progressContainer) {
|
|
// Create progress element for this file
|
|
const fileProgress = document.createElement("div");
|
|
fileProgress.className = "flex flex-col mb-2";
|
|
fileProgress.innerHTML = `
|
|
<div class="flex justify-between">
|
|
<span class="text-sm truncate" title="${file.name}">${file.name}</span>
|
|
<span class="text-xs text-gray-500">${formatFileSize(file.size)}</span>
|
|
</div>
|
|
<div class="w-full bg-gray-200 h-2 rounded-full mt-1">
|
|
<div class="file-progress-bar bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
|
|
</div>
|
|
<div class="file-status text-xs text-gray-600 mt-1">Validating...</div>
|
|
`;
|
|
progressContainer.appendChild(fileProgress);
|
|
|
|
const progressBar = fileProgress.querySelector(".file-progress-bar");
|
|
const statusEl = fileProgress.querySelector(".file-status");
|
|
|
|
// Validate file type by checking both MIME type and extension
|
|
const isValidMimeType = ACCEPTED_TYPES[file.type] || false;
|
|
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
|
|
const isValidExtension = ACCEPTED_EXTENSIONS.includes(fileExtension);
|
|
|
|
if (!isValidMimeType && !isValidExtension) {
|
|
statusEl.textContent = `Error: ${file.name} - Unsupported file type`;
|
|
statusEl.className = "text-xs text-red-500 mt-1";
|
|
return;
|
|
}
|
|
|
|
// Validate file size
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
statusEl.textContent = `Error: ${file.name} - File size exceeds 500MB limit`;
|
|
statusEl.className = "text-xs text-red-500 mt-1";
|
|
return;
|
|
}
|
|
|
|
// Upload the file
|
|
uploadFile(file, progressBar, statusEl);
|
|
}
|
|
|
|
async function uploadFile(file, progressBar, statusEl) {
|
|
statusEl.textContent = `Uploading...`;
|
|
try {
|
|
let formData = new FormData();
|
|
formData.append("file", file);
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "/api/ui-upload", true);
|
|
|
|
xhr.upload.onprogress = (e) => {
|
|
if (e.lengthComputable) {
|
|
const percentComplete = (e.loaded / e.total) * 100;
|
|
progressBar.style.width = percentComplete + "%";
|
|
statusEl.textContent = `Uploading: ${Math.round(percentComplete)}%`;
|
|
}
|
|
};
|
|
|
|
xhr.onload = function() {
|
|
if (xhr.status === 200) {
|
|
const result = JSON.parse(xhr.responseText);
|
|
progressBar.style.width = "100%";
|
|
progressBar.className = "file-progress-bar bg-green-500 h-2 rounded-full";
|
|
statusEl.textContent = `Success: Task ID: ${result.task_id}`;
|
|
statusEl.className = "text-xs text-green-600 mt-1";
|
|
updateOverallStatus();
|
|
} else {
|
|
throw new Error(`Upload failed with status ${xhr.status}`);
|
|
}
|
|
};
|
|
|
|
xhr.onerror = function() {
|
|
throw new Error("Network error occurred");
|
|
};
|
|
|
|
xhr.send(formData);
|
|
|
|
} catch (err) {
|
|
statusEl.textContent = `Error: ${err.message}`;
|
|
statusEl.className = "text-xs text-red-500 mt-1";
|
|
progressBar.className = "file-progress-bar bg-red-500 h-2 rounded-full";
|
|
updateOverallStatus();
|
|
}
|
|
}
|
|
|
|
function updateOverallStatus() {
|
|
// Count success/failure
|
|
const fileStatuses = document.querySelectorAll('.file-status');
|
|
let completed = 0;
|
|
let total = fileStatuses.length;
|
|
|
|
fileStatuses.forEach(status => {
|
|
if (status.textContent.includes('Success') || status.textContent.includes('Error')) {
|
|
completed++;
|
|
}
|
|
});
|
|
|
|
if (completed === total) {
|
|
statusMessage.textContent = `All uploads completed (${completed}/${total})`;
|
|
} else {
|
|
statusMessage.textContent = `Uploading files (${completed}/${total})`;
|
|
}
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
</script>
|
|
{% endblock %}
|