refactor(views): split file views into summary, detail, process, and annotations pages

- /files/<id> → new summary page with navigation cards
- /files/<id>/detail → document detail with metadata, preview, text
- /files/<id>/process → processing pipeline status and history
- /files/<id>/annotations → comments & annotations with EmbedPDF viewer
- /files/<id>/comments → redirects to /annotations
- Added embed-pdf-viewer as git submodule for PDF annotation viewer
- Updated all navigation links across templates
- Updated all tests to use new URL structure

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/12276514-bd3d-4e3e-84d9-5977d1f82b19
This commit is contained in:
copilot-swe-agent[bot]
2026-03-22 11:48:57 +00:00
parent 5a3ddcc1f0
commit f852ba9783
14 changed files with 1213 additions and 725 deletions
+196
View File
@@ -0,0 +1,196 @@
{% extends "base.html" %}
{% block title %}{{ file.document_title or file.original_filename or 'Document' }} - DocuElevate{% endblock %}
{% block head_extra %}
<script src="/static/js/common.js"></script>
<style>
.summary-container { max-width: 900px; margin: 0 auto; }
/* ── header ── */
.summary-header {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap;
}
.summary-title { font-size: 1.75rem; font-weight: 700; color: #1f2937; line-height: 1.25; }
.summary-subtitle { font-size: 0.9rem; color: #6b7280; margin-top: 0.25rem; font-family: monospace; }
.dark .summary-title { color: #f3f4f6; }
.dark .summary-subtitle { color: #9ca3af; }
/* ── status pill ── */
.status-pill {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.35rem 0.85rem; border-radius: 9999px;
font-size: 0.8rem; font-weight: 600; white-space: nowrap;
}
.pill-completed { background: #d1fae5; color: #065f46; }
.pill-failed { background: #fee2e2; color: #991b1b; }
.pill-processing { background: #dbeafe; color: #1e3a8a; }
.pill-pending { background: #fef3c7; color: #92400e; }
.pill-duplicate { background: #e5e7eb; color: #374151; }
/* ── cards ── */
.summary-card {
background: white; border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,.08);
padding: 1.5rem; margin-bottom: 1.25rem;
}
.dark .summary-card { background: #1f2937; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
.summary-card-title {
font-size: 1rem; font-weight: 700; color: #374151;
text-transform: uppercase; letter-spacing: 0.05em;
margin-bottom: 1rem;
}
.dark .summary-card-title { color: #d1d5db; }
/* ── info rows ── */
.info-row { display: flex; justify-content: space-between; padding: 0.4rem 0; border-bottom: 1px solid #f3f4f6; font-size: 0.875rem; }
.info-row:last-child { border-bottom: none; }
.info-key { color: #6b7280; }
.info-val { color: #1f2937; font-family: monospace; text-align: right; word-break: break-all; max-width: 60%; }
.dark .info-row { border-bottom-color: #374151; }
.dark .info-key { color: #9ca3af; }
.dark .info-val { color: #e5e7eb; }
/* ── nav cards ── */
.nav-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.25rem; }
@media (max-width: 768px) { .nav-grid { grid-template-columns: 1fr; } }
.nav-card {
display: flex; flex-direction: column; align-items: center; justify-content: center;
background: white; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,.08);
padding: 1.5rem; text-decoration: none; color: #374151;
transition: box-shadow 0.15s, transform 0.15s; min-height: 120px;
}
.nav-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.15); transform: translateY(-2px); }
.dark .nav-card { background: #1f2937; color: #e5e7eb; }
.dark .nav-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.4); }
.nav-card i { font-size: 1.5rem; margin-bottom: 0.75rem; }
.nav-card-title { font-weight: 700; font-size: 0.95rem; margin-bottom: 0.25rem; }
.nav-card-desc { font-size: 0.8rem; color: #6b7280; text-align: center; }
.dark .nav-card-desc { color: #9ca3af; }
/* ── action buttons ── */
.action-btn {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1.1rem; border-radius: 0.375rem;
font-size: 0.875rem; font-weight: 600; text-decoration: none;
cursor: pointer; border: none; transition: filter 0.1s;
}
.action-btn:hover { filter: brightness(0.92); }
.btn-primary { background: #3b82f6; color: white; }
.btn-secondary { background: #f3f4f6; color: #374151; }
/* ── error ── */
.error-box { background: #fee2e2; border: 1px solid #f87171; color: #b91c1c; padding: 1rem; border-radius: 0.375rem; margin-bottom: 1rem; }
</style>
{% endblock %}
{% block content %}
<div class="summary-container">
{% if error %}
<div class="error-box"><strong>Error:</strong> {{ error }}</div>
{% elif file %}
<!-- ── Back + header ── -->
<a href="/files" style="display:inline-flex;align-items:center;gap:0.4rem;color:#3b82f6;font-weight:600;text-decoration:none;margin-bottom:1.25rem;" aria-label="Back to Files">
<i class="fas fa-arrow-left" aria-hidden="true"></i> Back to Files
</a>
<div class="summary-header">
<div>
<div class="summary-title">
{% if gpt_metadata and gpt_metadata.filename %}
{{ gpt_metadata.filename | replace('.pdf','') | replace('_',' ') }}
{% elif file.document_title %}
{{ file.document_title }}
{% else %}
{{ file.original_filename or '(untitled)' }}
{% endif %}
</div>
<div class="summary-subtitle">{{ file.original_filename }}</div>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;">
<!-- Status pill -->
{% if file.is_duplicate %}
<span class="status-pill pill-duplicate"><i class="fas fa-copy" aria-hidden="true"></i> Duplicate</span>
{% elif step_summary %}
{% set main_completed = step_summary.main.success + step_summary.main.skipped %}
{% if step_summary.main.failure > 0 or step_summary.uploads.failure > 0 %}
<span class="status-pill pill-failed"><i class="fas fa-times-circle" aria-hidden="true"></i> Failed</span>
{% elif step_summary.main["in_progress"] > 0 or step_summary.uploads["in_progress"] > 0 %}
<span class="status-pill pill-processing"><i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> Processing</span>
{% elif step_summary.total_main_steps > 0 and main_completed == step_summary.total_main_steps and step_summary.main.failure == 0 %}
<span class="status-pill pill-completed"><i class="fas fa-check-circle" aria-hidden="true"></i> Completed</span>
{% else %}
<span class="status-pill pill-pending"><i class="fas fa-pause-circle" aria-hidden="true"></i> Pending</span>
{% endif %}
{% endif %}
</div>
</div>
<!-- ── Navigation cards ── -->
<nav class="nav-grid" aria-label="File sections">
<a href="/files/{{ file.id }}/detail" class="nav-card">
<i class="fas fa-file-alt" aria-hidden="true" style="color:#3b82f6;"></i>
<div class="nav-card-title">Document Detail</div>
<div class="nav-card-desc">Metadata, preview &amp; extracted text</div>
</a>
<a href="/files/{{ file.id }}/process" class="nav-card">
<i class="fas fa-cogs" aria-hidden="true" style="color:#6366f1;"></i>
<div class="nav-card-title">Processing</div>
<div class="nav-card-desc">Pipeline status &amp; processing history</div>
</a>
<a href="/files/{{ file.id }}/annotations" class="nav-card">
<i class="fas fa-comments" aria-hidden="true" style="color:#10b981;"></i>
<div class="nav-card-title">Comments &amp; Annotations</div>
<div class="nav-card-desc">Discussion &amp; document annotations</div>
</a>
</nav>
<!-- ── File info card ── -->
<div class="summary-card">
<div class="summary-card-title"><i class="fas fa-info-circle" aria-hidden="true" style="color:#6366f1;margin-right:0.4rem;"></i>File Information</div>
<div class="info-row"><span class="info-key">File ID</span><span class="info-val">{{ file.id }}</span></div>
<div class="info-row"><span class="info-key">Filename</span><span class="info-val">{{ file.original_filename }}</span></div>
<div class="info-row"><span class="info-key">Size</span><span class="info-val">{{ (file.file_size / 1024) | round(1) }} KB</span></div>
<div class="info-row"><span class="info-key">MIME Type</span><span class="info-val">{{ file.mime_type or 'unknown' }}</span></div>
<div class="info-row"><span class="info-key">Created</span><span class="info-val">{{ file.created_at.strftime('%Y-%m-%d %H:%M') if file.created_at else 'N/A' }}</span></div>
{% if file.detected_language %}
<div class="info-row"><span class="info-key">Language</span><span class="info-val">{{ file.detected_language }}</span></div>
{% endif %}
{% if pipeline_info %}
<div class="info-row"><span class="info-key">Pipeline</span><span class="info-val">{{ pipeline_info.name }}</span></div>
{% endif %}
{% if file.document_title %}
<div class="info-row"><span class="info-key">Document Title</span><span class="info-val">{{ file.document_title }}</span></div>
{% endif %}
</div>
<!-- ── Quick actions ── -->
<div class="summary-card">
<div class="summary-card-title"><i class="fas fa-bolt" aria-hidden="true" style="color:#f59e0b;margin-right:0.4rem;"></i>Quick Actions</div>
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;">
{% if processed_file_exists %}
<a href="/api/files/{{ file.id }}/download?version=processed" class="action-btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i> Download Processed
</a>
{% endif %}
{% if original_file_exists %}
<a href="/api/files/{{ file.id }}/download?version=original" class="action-btn btn-secondary">
<i class="fas fa-download" aria-hidden="true"></i> Download Original
</a>
{% endif %}
<a href="/files/{{ file.id }}/detail" class="action-btn btn-secondary">
<i class="fas fa-eye" aria-hidden="true"></i> View Detail
</a>
</div>
</div>
{% else %}
<!-- file is None without error -->
<div class="error-box">Document not found.</div>
{% endif %}
</div>
{% endblock %}