From 4e654a9f08207da1f30303c62fbd712a6350076c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:10:55 +0000 Subject: [PATCH 1/4] Initial plan From ed0bac7345e3af6fd06c18fa14062987a3bf6bbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:12:44 +0000 Subject: [PATCH 2/4] feat(ui): add auto-refresh after upload and inline file preview - Auto-refresh files table after successful uploads using custom event - Add inline preview support for PDFs, images, and text files - Set Content-Disposition header to inline for preview endpoint - Add download button as secondary action in file details view Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/api/files.py | 2 +- frontend/static/js/upload.js | 6 ++++++ frontend/templates/file_detail.html | 24 ++++++++++++++++++++---- frontend/templates/files.html | 26 ++++++++------------------ 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/app/api/files.py b/app/api/files.py index de4fa541..ab1ba50a 100644 --- a/app/api/files.py +++ b/app/api/files.py @@ -568,7 +568,7 @@ def get_file_preview( return FileResponse( path=file_path, media_type=file_record.mime_type or "application/pdf", - filename=file_record.original_filename, + headers={"Content-Disposition": f'inline; filename="{file_record.original_filename}"'}, ) except HTTPException: diff --git a/frontend/static/js/upload.js b/frontend/static/js/upload.js index 48be819c..13786cf7 100644 --- a/frontend/static/js/upload.js +++ b/frontend/static/js/upload.js @@ -195,6 +195,12 @@ function updateOverallStatus(statusMessage) { if (completed === total) { statusMessage.textContent = `All uploads completed (${completed}/${total})`; + + // Trigger a custom event when all uploads are complete + const allUploadsComplete = new CustomEvent('allUploadsComplete', { + detail: { total: total, completed: completed } + }); + window.dispatchEvent(allUploadsComplete); } else { statusMessage.textContent = `Uploading files (${completed}/${total})`; } diff --git a/frontend/templates/file_detail.html b/frontend/templates/file_detail.html index 3996422f..93c05010 100644 --- a/frontend/templates/file_detail.html +++ b/frontend/templates/file_detail.html @@ -863,12 +863,24 @@

Original File

- + {% if file.mime_type and file.mime_type.startswith('image/') %} + + {{ file.original_filename }} + {% elif file.mime_type and file.mime_type.startswith('text/') %} + + + {% else %} + + + {% endif %}
-
+
Open in new tab + + Download +
{% endif %} @@ -877,12 +889,16 @@

Processed File

- + +
-
+
Open in new tab + + Download +
{% endif %} diff --git a/frontend/templates/files.html b/frontend/templates/files.html index 0d780e16..e27e295b 100644 --- a/frontend/templates/files.html +++ b/frontend/templates/files.html @@ -835,27 +835,17 @@ // Process the dropped files processFiles(e.dataTransfer.files, uploadProgressContainer, uploadStatusMessage); - - // Optionally reload page after uploads complete (with a delay) - setTimeout(() => { - const fileStatuses = document.querySelectorAll('.file-status'); - let allCompleted = true; - fileStatuses.forEach(status => { - if (!status.textContent.includes('Success') && !status.textContent.includes('Error')) { - allCompleted = false; - } - }); - - if (allCompleted && fileStatuses.length > 0) { - // Refresh the page after a short delay to show the new files - setTimeout(() => { - window.location.reload(); - }, 2000); - } - }, 1000); } }); + // Listen for upload completion event and reload the page to show new files + window.addEventListener('allUploadsComplete', (e) => { + // Wait 2 seconds to let users see the success message + setTimeout(() => { + window.location.reload(); + }, 2000); + }); + function closeUploadModal() { uploadModal.classList.remove('active'); } From 98fe5ab864f05f63b5012ce9bdcbbf023bf53dc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:14:45 +0000 Subject: [PATCH 3/4] test: add inline preview header verification test Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- tests/test_file_detail_endpoints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_file_detail_endpoints.py b/tests/test_file_detail_endpoints.py index 1cdc1f0f..d7b52233 100644 --- a/tests/test_file_detail_endpoints.py +++ b/tests/test_file_detail_endpoints.py @@ -150,6 +150,9 @@ class TestFilePreview: response = client.get(f"/api/files/{file_record.id}/preview?version=original") assert response.status_code == 200 assert response.headers["content-type"].startswith("application/pdf") + # Verify file is set to display inline, not download + assert "content-disposition" in response.headers + assert "inline" in response.headers["content-disposition"] def test_preview_processed_file_not_found(self, client: TestClient, db_session, sample_pdf_path): """Test getting processed file preview when it doesn't exist.""" From 334714423e4ea25dc0d0d8da33fe9a5badf6be13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:16:31 +0000 Subject: [PATCH 4/4] fix(ui): address code review feedback on inline preview - Remove invalid type attribute from iframe elements - Add dedicated download endpoint with attachment disposition - Update download links to use new endpoint instead of preview Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/api/files.py | 72 +++++++++++++++++++++++++++++ frontend/templates/file_detail.html | 8 ++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/app/api/files.py b/app/api/files.py index ab1ba50a..e642aaab 100644 --- a/app/api/files.py +++ b/app/api/files.py @@ -578,6 +578,78 @@ def get_file_preview( raise HTTPException(status_code=500, detail=f"Error retrieving file preview: {str(e)}") +@router.get("/files/{file_id}/download") +@require_login +def download_file( + request: Request, + file_id: int, + version: str = Query("original", description="original or processed"), + db: Session = Depends(get_db), +): + """ + Download file (original or processed version) as attachment. + + Args: + file_id: ID of the file + version: "original" for tmp file, "processed" for processed file + + Returns: + File content as attachment download + """ + from fastapi.responses import FileResponse + + try: + # Find the file record + file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() + + if not file_record: + raise HTTPException(status_code=404, detail=f"File with ID {file_id} not found") + + if version == "original": + # Return the original file from tmp + if not file_record.local_filename or not os.path.exists(file_record.local_filename): + raise HTTPException(status_code=404, detail="Original file not found on disk") + + file_path = file_record.local_filename + + elif version == "processed": + # Look for processed file in /workdir/processed/ + workdir = settings.workdir + processed_dir = os.path.join(workdir, "processed") + + # Try to find the processed file (same hash or UUID-based naming) + base_filename = os.path.splitext(file_record.original_filename)[0] + potential_paths = [ + os.path.join(processed_dir, f"{file_record.filehash}.pdf"), + os.path.join(processed_dir, f"{base_filename}_processed.pdf"), + os.path.join(processed_dir, file_record.original_filename), + ] + + file_path = None + for path in potential_paths: + if os.path.exists(path): + file_path = path + break + + if not file_path: + raise HTTPException(status_code=404, detail="Processed file not found") + else: + raise HTTPException(status_code=400, detail="Invalid version parameter. Use 'original' or 'processed'") + + # Return the file with attachment disposition to trigger download + return FileResponse( + path=file_path, + media_type=file_record.mime_type or "application/pdf", + headers={"Content-Disposition": f'attachment; filename="{file_record.original_filename}"'}, + ) + + except HTTPException: + raise + except Exception as e: + logger.exception(f"Error downloading file: {str(e)}") + raise HTTPException(status_code=500, detail=f"Error downloading file: {str(e)}") + + @router.post("/ui-upload") @require_login async def ui_upload(request: Request, file: UploadFile = File(...)): diff --git a/frontend/templates/file_detail.html b/frontend/templates/file_detail.html index 93c05010..35203cbf 100644 --- a/frontend/templates/file_detail.html +++ b/frontend/templates/file_detail.html @@ -871,14 +871,14 @@ {% else %} - + {% endif %}
Open in new tab - + Download
@@ -890,13 +890,13 @@

Processed File

- +
Open in new tab - + Download