1306 lines
No EOL
47 KiB
HTML
1306 lines
No EOL
47 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>PDF DocTags Analyzer</title>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.12.0/toastify.min.js"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.12.0/toastify.min.css">
|
|
<style>
|
|
:root {
|
|
--primary-color: #3B82F6; /* Blue 500 */
|
|
--primary-hover: #2563EB; /* Blue 600 */
|
|
--secondary-color: #10B981; /* Green 500 */
|
|
--secondary-hover: #059669; /* Green 600 */
|
|
--danger-color: #EF4444; /* Red 500 */
|
|
--gray-100: #F3F4F6;
|
|
--gray-200: #E5E7EB;
|
|
--gray-300: #D1D5DB;
|
|
--gray-700: #374151;
|
|
--gray-800: #1F2937;
|
|
--gray-900: #111827;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
line-height: 1.5;
|
|
color: var(--gray-800);
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
header {
|
|
background-color: var(--gray-900);
|
|
color: white;
|
|
padding: 1rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.25rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
button {
|
|
cursor: pointer;
|
|
border: none;
|
|
border-radius: 0.25rem;
|
|
padding: 0.5rem 1rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: var(--primary-hover);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: var(--secondary-color);
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: var(--secondary-hover);
|
|
}
|
|
|
|
.btn-outline {
|
|
background-color: transparent;
|
|
border: 1px solid var(--gray-300);
|
|
}
|
|
|
|
.btn-outline:hover {
|
|
background-color: var(--gray-100);
|
|
}
|
|
|
|
.icon {
|
|
width: 1rem;
|
|
height: 1rem;
|
|
}
|
|
|
|
.flex-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
}
|
|
|
|
.flex-col {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.gap-2 {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.gap-4 {
|
|
gap: 1rem;
|
|
}
|
|
|
|
main {
|
|
flex: 1;
|
|
display: flex;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.settings-panel {
|
|
background-color: var(--gray-100);
|
|
padding: 1rem;
|
|
border-bottom: 1px solid var(--gray-300);
|
|
display: none;
|
|
}
|
|
|
|
.settings-panel.open {
|
|
display: block;
|
|
}
|
|
|
|
.settings-panel h2 {
|
|
font-size: 1rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.settings-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
font-size: 0.875rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.form-group input[type="number"] {
|
|
width: 100%;
|
|
padding: 0.5rem;
|
|
border: 1px solid var(--gray-300);
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
.checkbox-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.workspace {
|
|
flex: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 1rem;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.canvas-container {
|
|
flex: 1;
|
|
background-color: var(--gray-200);
|
|
border: 1px solid var(--gray-300);
|
|
border-radius: 0.25rem;
|
|
overflow: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
}
|
|
|
|
#pdfCanvas {
|
|
background-color: white;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
.footer-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.file-info {
|
|
font-size: 0.875rem;
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.sidebar {
|
|
flex: 1;
|
|
max-width: 400px;
|
|
border-left: 1px solid var(--gray-300);
|
|
background-color: var(--gray-100);
|
|
padding: 1rem;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.sidebar h2 {
|
|
font-size: 1.125rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.status-card {
|
|
padding: 0.75rem;
|
|
border-radius: 0.25rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.status-card.loading {
|
|
background-color: rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.status-card.success {
|
|
background-color: rgba(16, 185, 129, 0.1);
|
|
}
|
|
|
|
.zone-list {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.zone-item {
|
|
margin-bottom: 0.75rem;
|
|
padding-bottom: 0.75rem;
|
|
border-bottom: 1px solid var(--gray-300);
|
|
}
|
|
|
|
.zone-label {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.25rem;
|
|
color: white;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.zone-coords {
|
|
font-size: 0.75rem;
|
|
color: var(--gray-700);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.zone-content {
|
|
font-size: 0.875rem;
|
|
padding-left: 0.5rem;
|
|
}
|
|
|
|
.legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.legend-color {
|
|
width: 0.75rem;
|
|
height: 0.75rem;
|
|
border-radius: 0.125rem;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.spinner {
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 50%;
|
|
border-top: 2px solid white;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
.command-preview {
|
|
margin-top: 1rem;
|
|
padding: 0.75rem;
|
|
background-color: rgba(59, 130, 246, 0.1);
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
.command-preview h3 {
|
|
font-size: 0.875rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.code-block {
|
|
background-color: var(--gray-900);
|
|
color: white;
|
|
padding: 0.75rem;
|
|
border-radius: 0.25rem;
|
|
font-family: monospace;
|
|
font-size: 0.75rem;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.welcome {
|
|
max-width: 800px;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
background-color: white;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
.welcome h2 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.welcome p {
|
|
color: var(--gray-700);
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.feature-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
margin-top: 2rem;
|
|
text-align: left;
|
|
}
|
|
|
|
.feature-item {
|
|
background-color: var(--gray-100);
|
|
padding: 1rem;
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
.feature-item h3 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.feature-item p {
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.zone-overlay {
|
|
position: absolute;
|
|
border: 2px solid;
|
|
pointer-events: none;
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
.zone-overlay-label {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
font-size: 10px;
|
|
padding: 2px 4px;
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
border-bottom-right-radius: 3px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* For smaller screens */
|
|
@media (max-width: 1024px) {
|
|
main {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.sidebar {
|
|
max-width: 100%;
|
|
border-left: none;
|
|
border-top: 1px solid var(--gray-300);
|
|
}
|
|
|
|
.settings-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>PDF DocTags Analyzer</h1>
|
|
<div class="flex-row gap-2">
|
|
<button id="uploadBtn" class="btn-primary">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="17 8 12 3 7 8"></polyline>
|
|
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
</svg>
|
|
Upload PDF
|
|
</button>
|
|
<input type="file" id="fileInput" accept="application/pdf" style="display: none;">
|
|
<button id="settingsBtn" class="btn-outline">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="3"></circle>
|
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
</svg>
|
|
Settings
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="settings-panel" id="settingsPanel">
|
|
<h2>Processing Settings</h2>
|
|
<div class="settings-grid">
|
|
<div class="form-group">
|
|
<label for="dpi">DPI</label>
|
|
<input type="number" id="dpi" value="200" min="72" max="600">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="xFactor">X-Factor</label>
|
|
<input type="number" id="xFactor" value="0.7" min="0.1" max="2.0" step="0.1">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="yFactor">Y-Factor</label>
|
|
<input type="number" id="yFactor" value="0.7" min="0.1" max="2.0" step="0.1">
|
|
</div>
|
|
</div>
|
|
<div class="flex-row gap-4" style="margin-top: 0.5rem;">
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" id="showLabels" checked>
|
|
<label for="showLabels">Show Labels</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" id="showZones" checked>
|
|
<label for="showZones">Show Zones</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" id="transparent" checked>
|
|
<label for="transparent">Transparent Zones</label>
|
|
</div>
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" id="autoAdjust" checked>
|
|
<label for="autoAdjust">Auto-adjust Scaling</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<main id="appMain">
|
|
<!-- Welcome screen - shown when no PDF is loaded -->
|
|
<div class="welcome" id="welcomeScreen">
|
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
<polyline points="14 2 14 8 20 8"></polyline>
|
|
<line x1="16" y1="13" x2="8" y2="13"></line>
|
|
<line x1="16" y1="17" x2="8" y2="17"></line>
|
|
<polyline points="10 9 9 9 8 9"></polyline>
|
|
</svg>
|
|
<h2>Upload a PDF to Get Started</h2>
|
|
<p>
|
|
The DocTags Analyzer will process your PDF document to identify and visualize
|
|
structured elements like headers, text blocks, tables, and images.
|
|
</p>
|
|
<button id="welcomeUploadBtn" class="btn-primary" style="padding: 0.75rem 1.5rem;">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="17 8 12 3 7 8"></polyline>
|
|
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
</svg>
|
|
Select PDF File
|
|
</button>
|
|
|
|
<div class="feature-grid">
|
|
<div class="feature-item">
|
|
<h3>
|
|
<svg class="icon" style="color: var(--primary-color);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
<circle cx="12" cy="12" r="4"></circle>
|
|
<line x1="4.93" y1="4.93" x2="9.17" y2="9.17"></line>
|
|
<line x1="14.83" y1="14.83" x2="19.07" y2="19.07"></line>
|
|
<line x1="14.83" y1="9.17" x2="19.07" y2="4.93"></line>
|
|
<line x1="14.83" y1="9.17" x2="18.36" y2="5.64"></line>
|
|
<line x1="4.93" y1="19.07" x2="9.17" y2="14.83"></line>
|
|
</svg>
|
|
Visualize Content
|
|
</h3>
|
|
<p>
|
|
See how your document is structured with visual overlays showing text blocks,
|
|
headers, images and tables.
|
|
</p>
|
|
</div>
|
|
<div class="feature-item">
|
|
<h3>
|
|
<svg class="icon" style="color: var(--primary-color);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
</svg>
|
|
Extract DocTags
|
|
</h3>
|
|
<p>
|
|
Generate structured DocTags markup that identifies document elements
|
|
with semantic information.
|
|
</p>
|
|
</div>
|
|
<div class="feature-item">
|
|
<h3>
|
|
<svg class="icon" style="color: var(--primary-color);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 2v6m0 0l3-3m-3 3L9 5"></path>
|
|
<circle cx="12" cy="14" r="3"></circle>
|
|
<path d="M3 9.75v4.5c0 4.5 3.6 8.25 9 8.25s9-3.75 9-8.25v-4.5"></path>
|
|
</svg>
|
|
Optimize Scale
|
|
</h3>
|
|
<p>
|
|
Find the optimal scaling factors to perfectly align the detected elements
|
|
with the original document.
|
|
</p>
|
|
</div>
|
|
<div class="feature-item">
|
|
<h3>
|
|
<svg class="icon" style="color: var(--primary-color);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="7 10 12 15 17 10"></polyline>
|
|
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
</svg>
|
|
Export Results
|
|
</h3>
|
|
<p>
|
|
Save visualizations as HTML and extract the processed content in multiple
|
|
formats including Markdown and HTML.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main workspace - shown when a PDF is loaded -->
|
|
<div id="workspace" class="workspace" style="display: none;">
|
|
<div class="toolbar">
|
|
<div class="flex-row gap-2">
|
|
<button id="zoomOutBtn" class="btn-outline">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
<line x1="8" y1="11" x2="14" y2="11"></line>
|
|
</svg>
|
|
</button>
|
|
<span id="zoomLevel">100%</span>
|
|
<button id="zoomInBtn" class="btn-outline">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
<line x1="11" y1="8" x2="11" y2="14"></line>
|
|
<line x1="8" y1="11" x2="14" y2="11"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex-row gap-2">
|
|
<button id="prevPageBtn" class="btn-outline">Previous</button>
|
|
<span id="pageInfo">Page 1 of 1</span>
|
|
<button id="nextPageBtn" class="btn-outline">Next</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="canvas-container" id="canvasContainer">
|
|
<canvas id="pdfCanvas"></canvas>
|
|
<div id="zoneOverlays"></div>
|
|
</div>
|
|
|
|
<div class="footer-bar">
|
|
<div id="fileInfo" class="file-info"></div>
|
|
<div class="flex-row gap-2">
|
|
<button id="analyzeBtn" class="btn-secondary">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
</svg>
|
|
Analyze Page
|
|
</button>
|
|
<button id="findOptimalBtn" class="btn-outline">
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 2v6m0 0l3-3m-3 3L9 5"></path>
|
|
<circle cx="12" cy="14" r="3"></circle>
|
|
<path d="M3 9.75v4.5c0 4.5 3.6 8.25 9 8.25s9-3.75 9-8.25v-4.5"></path>
|
|
</svg>
|
|
Find Optimal Scale
|
|
</button>
|
|
<button id="exportBtn" class="btn-outline" disabled>
|
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="7 10 12 15 17 10"></polyline>
|
|
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
</svg>
|
|
Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="sidebar" class="sidebar" style="display: none;">
|
|
<div>
|
|
<h2>Status</h2>
|
|
<div id="statusCard" class="status-card success">
|
|
Ready to process PDF
|
|
</div>
|
|
</div>
|
|
|
|
<div id="resultsSection" style="display: none;">
|
|
<h2>Detected Elements</h2>
|
|
<div class="legend" id="zoneLegend">
|
|
<!-- Legend will be populated by JavaScript -->
|
|
</div>
|
|
<div class="zone-list" id="zoneList">
|
|
<!-- Zone items will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="command-preview">
|
|
<h3>Command Preview</h3>
|
|
<pre class="code-block" id="commandPreview">python analyzer.py --image "your_file.pdf" --page 1 --dpi 200 &&
|
|
python fix_scaling.py --doctags output.doctags.txt --output fixed_output.doctags.txt --x-factor 0.7 --y-factor 0.7 &&
|
|
python visualizer.py --doctags fixed_output.doctags.txt --pdf "your_file.pdf" --page 1 --adjust --show</pre>
|
|
</div>
|
|
|
|
<div style="margin-top: 1.5rem;">
|
|
<h2>Processing Help</h2>
|
|
<div style="font-size: 0.875rem; margin-top: 0.5rem;">
|
|
<p>The DocTags analyzer extracts structured elements from your PDF and visualizes them.</p>
|
|
<ul style="margin-left: 1.25rem; margin-top: 0.5rem;">
|
|
<li>Set DPI higher (300-600) for better quality extraction</li>
|
|
<li>Adjust X/Y Factors to fix alignment issues</li>
|
|
<li>Use the "Find Optimal Scale" option for the best results</li>
|
|
<li>The Auto-adjust option will try to align zones automatically</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
// PDF.js initialization
|
|
const pdfjsLib = window['pdfjs-dist/build/pdf'];
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js';
|
|
|
|
// DOM elements
|
|
const fileInput = document.getElementById('fileInput');
|
|
const uploadBtn = document.getElementById('uploadBtn');
|
|
const welcomeUploadBtn = document.getElementById('welcomeUploadBtn');
|
|
const settingsBtn = document.getElementById('settingsBtn');
|
|
const settingsPanel = document.getElementById('settingsPanel');
|
|
const welcomeScreen = document.getElementById('welcomeScreen');
|
|
const workspace = document.getElementById('workspace');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const canvasContainer = document.getElementById('canvasContainer');
|
|
const pdfCanvas = document.getElementById('pdfCanvas');
|
|
const zoneOverlays = document.getElementById('zoneOverlays');
|
|
const ctx = pdfCanvas.getContext('2d');
|
|
|
|
// Settings
|
|
const dpiInput = document.getElementById('dpi');
|
|
const xFactorInput = document.getElementById('xFactor');
|
|
const yFactorInput = document.getElementById('yFactor');
|
|
const showLabelsCheck = document.getElementById('showLabels');
|
|
const showZonesCheck = document.getElementById('showZones');
|
|
const transparentCheck = document.getElementById('transparent');
|
|
const autoAdjustCheck = document.getElementById('autoAdjust');
|
|
|
|
// Controls
|
|
const zoomOutBtn = document.getElementById('zoomOutBtn');
|
|
const zoomInBtn = document.getElementById('zoomInBtn');
|
|
const zoomLevel = document.getElementById('zoomLevel');
|
|
const prevPageBtn = document.getElementById('prevPageBtn');
|
|
const nextPageBtn = document.getElementById('nextPageBtn');
|
|
const pageInfo = document.getElementById('pageInfo');
|
|
const fileInfo = document.getElementById('fileInfo');
|
|
const analyzeBtn = document.getElementById('analyzeBtn');
|
|
const findOptimalBtn = document.getElementById('findOptimalBtn');
|
|
const exportBtn = document.getElementById('exportBtn');
|
|
const statusCard = document.getElementById('statusCard');
|
|
const zoneLegend = document.getElementById('zoneLegend');
|
|
const zoneList = document.getElementById('zoneList');
|
|
const resultsSection = document.getElementById('resultsSection');
|
|
const commandPreview = document.getElementById('commandPreview');
|
|
|
|
// Zone colors for different types
|
|
const zoneColors = {
|
|
'section_header_level_1': '#FF5722', // Orange
|
|
'text': '#2196F3', // Blue
|
|
'picture': '#4CAF50', // Green
|
|
'table': '#9C27B0', // Purple
|
|
'page_header': '#FFC107', // Amber
|
|
'page_footer': '#795548', // Brown
|
|
'default': '#607D8B' // Blue Grey
|
|
};
|
|
|
|
// Application state
|
|
let appState = {
|
|
pdf: null,
|
|
file: null,
|
|
fileName: "",
|
|
fileSize: 0,
|
|
currentPage: 1,
|
|
totalPages: 0,
|
|
scale: 1.5,
|
|
zoom: 1.0,
|
|
isLoading: false,
|
|
isAnalyzed: false,
|
|
processingStep: "",
|
|
zones: [],
|
|
pdfDataBase64: null,
|
|
processingResults: null
|
|
};
|
|
|
|
// Initialize application
|
|
function init() {
|
|
// Event listeners for buttons
|
|
uploadBtn.addEventListener('click', () => fileInput.click());
|
|
welcomeUploadBtn.addEventListener('click', () => fileInput.click());
|
|
fileInput.addEventListener('change', handleFileSelect);
|
|
settingsBtn.addEventListener('click', toggleSettings);
|
|
|
|
// Zoom and page navigation
|
|
zoomOutBtn.addEventListener('click', () => setZoom(appState.zoom - 0.1));
|
|
zoomInBtn.addEventListener('click', () => setZoom(appState.zoom + 0.1));
|
|
prevPageBtn.addEventListener('click', () => changePage(appState.currentPage - 1));
|
|
nextPageBtn.addEventListener('click', () => changePage(appState.currentPage + 1));
|
|
|
|
// Processing buttons
|
|
analyzeBtn.addEventListener('click', analyzeCurrentPage);
|
|
findOptimalBtn.addEventListener('click', findOptimalScale);
|
|
exportBtn.addEventListener('click', exportResults);
|
|
|
|
// Settings change handlers
|
|
dpiInput.addEventListener('change', updateCommandPreview);
|
|
xFactorInput.addEventListener('change', updateCommandPreview);
|
|
yFactorInput.addEventListener('change', updateCommandPreview);
|
|
autoAdjustCheck.addEventListener('change', updateCommandPreview);
|
|
|
|
// Zone visibility controls
|
|
showLabelsCheck.addEventListener('change', updateZoneVisibility);
|
|
showZonesCheck.addEventListener('change', updateZoneVisibility);
|
|
transparentCheck.addEventListener('change', updateZoneVisibility);
|
|
|
|
// Initial view
|
|
updateCommandPreview();
|
|
}
|
|
|
|
// Handle file selection
|
|
function handleFileSelect(e) {
|
|
const file = e.target.files[0];
|
|
if (file && file.type === 'application/pdf') {
|
|
loadPDF(file);
|
|
} else if (file) {
|
|
showToast('Please select a valid PDF file', 'error');
|
|
}
|
|
}
|
|
|
|
// Toggle settings panel
|
|
function toggleSettings() {
|
|
settingsPanel.classList.toggle('open');
|
|
}
|
|
|
|
// Load PDF file
|
|
async function loadPDF(file) {
|
|
setLoading(true, 'Loading PDF...');
|
|
|
|
try {
|
|
// Save file information
|
|
appState.file = file;
|
|
appState.fileName = file.name;
|
|
appState.fileSize = file.size;
|
|
|
|
// Convert file to base64 for passing to backend
|
|
appState.pdfDataBase64 = await fileToBase64(file);
|
|
|
|
// Load PDF with PDF.js
|
|
const fileReader = new FileReader();
|
|
fileReader.onload = async function(event) {
|
|
const typedArray = new Uint8Array(event.target.result);
|
|
try {
|
|
const pdf = await pdfjsLib.getDocument({data: typedArray}).promise;
|
|
appState.pdf = pdf;
|
|
appState.totalPages = pdf.numPages;
|
|
appState.currentPage = 1;
|
|
|
|
// Update UI
|
|
switchToWorkspace();
|
|
updatePageInfo();
|
|
updateFileInfo();
|
|
renderPage();
|
|
|
|
setLoading(false, 'PDF loaded successfully. Ready to analyze.');
|
|
} catch (error) {
|
|
console.error('Error loading PDF:', error);
|
|
setLoading(false, 'Error loading PDF');
|
|
showToast('Failed to load PDF: ' + error.message, 'error');
|
|
}
|
|
};
|
|
fileReader.readAsArrayBuffer(file);
|
|
} catch (error) {
|
|
console.error('Error processing file:', error);
|
|
setLoading(false, 'Error processing file');
|
|
showToast('Failed to process file: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Convert file to base64
|
|
function fileToBase64(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.readAsDataURL(file);
|
|
reader.onload = () => resolve(reader.result.split(',')[1]);
|
|
reader.onerror = error => reject(error);
|
|
});
|
|
}
|
|
|
|
// Switch from welcome screen to workspace
|
|
function switchToWorkspace() {
|
|
welcomeScreen.style.display = 'none';
|
|
workspace.style.display = 'flex';
|
|
sidebar.style.display = 'block';
|
|
}
|
|
|
|
// Update page information display
|
|
function updatePageInfo() {
|
|
pageInfo.textContent = `Page ${appState.currentPage} of ${appState.totalPages}`;
|
|
prevPageBtn.disabled = appState.currentPage <= 1;
|
|
nextPageBtn.disabled = appState.currentPage >= appState.totalPages;
|
|
}
|
|
|
|
// Update file information display
|
|
function updateFileInfo() {
|
|
const fileSizeFormatted = formatFileSize(appState.fileSize);
|
|
fileInfo.textContent = `${appState.fileName} (${fileSizeFormatted})`;
|
|
}
|
|
|
|
// Format file size in KB, MB, etc.
|
|
function formatFileSize(bytes) {
|
|
if (bytes < 1024) return bytes + ' B';
|
|
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
|
else return (bytes / 1048576).toFixed(1) + ' MB';
|
|
}
|
|
|
|
// Render current PDF page to canvas
|
|
async function renderPage() {
|
|
if (!appState.pdf) return;
|
|
|
|
try {
|
|
const page = await appState.pdf.getPage(appState.currentPage);
|
|
|
|
// Scale viewport to fit the container
|
|
const containerWidth = canvasContainer.clientWidth;
|
|
const containerHeight = canvasContainer.clientHeight;
|
|
|
|
const originalViewport = page.getViewport({scale: 1});
|
|
|
|
// Determine the scale to fit the container (with some margin)
|
|
const scaleX = (containerWidth - 40) / originalViewport.width;
|
|
const scaleY = (containerHeight - 40) / originalViewport.height;
|
|
appState.scale = Math.min(scaleX, scaleY);
|
|
|
|
// Apply zoom
|
|
const scaledViewport = page.getViewport({scale: appState.scale * appState.zoom});
|
|
|
|
// Set canvas dimensions
|
|
pdfCanvas.width = scaledViewport.width;
|
|
pdfCanvas.height = scaledViewport.height;
|
|
|
|
// Render PDF page
|
|
const renderContext = {
|
|
canvasContext: ctx,
|
|
viewport: scaledViewport
|
|
};
|
|
|
|
await page.render(renderContext).promise;
|
|
|
|
// Clear any existing zone overlays
|
|
zoneOverlays.innerHTML = '';
|
|
|
|
// If we have analyzed zones, render them
|
|
if (appState.isAnalyzed && appState.zones.length > 0) {
|
|
renderZones();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error rendering page:', error);
|
|
showToast('Failed to render page: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Change to a different page
|
|
function changePage(pageNumber) {
|
|
if (pageNumber < 1 || pageNumber > appState.totalPages) return;
|
|
|
|
appState.currentPage = pageNumber;
|
|
updatePageInfo();
|
|
renderPage();
|
|
|
|
// Reset analysis results when changing pages
|
|
appState.isAnalyzed = false;
|
|
appState.zones = [];
|
|
resultsSection.style.display = 'none';
|
|
exportBtn.disabled = true;
|
|
|
|
setLoading(false, 'Ready to analyze page ' + pageNumber);
|
|
}
|
|
|
|
// Set zoom level
|
|
function setZoom(level) {
|
|
appState.zoom = Math.max(0.5, Math.min(2.0, level));
|
|
zoomLevel.textContent = Math.round(appState.zoom * 100) + '%';
|
|
renderPage();
|
|
}
|
|
|
|
// Set loading state
|
|
function setLoading(isLoading, message) {
|
|
appState.isLoading = isLoading;
|
|
appState.processingStep = message;
|
|
|
|
// Update status card
|
|
statusCard.className = isLoading ? 'status-card loading' : 'status-card success';
|
|
|
|
if (isLoading) {
|
|
statusCard.innerHTML = `
|
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
<div class="spinner"></div>
|
|
<span>${message}</span>
|
|
</div>
|
|
`;
|
|
analyzeBtn.disabled = true;
|
|
findOptimalBtn.disabled = true;
|
|
} else {
|
|
statusCard.textContent = message;
|
|
analyzeBtn.disabled = false;
|
|
findOptimalBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
// Show toast notification
|
|
function showToast(message, type = 'info') {
|
|
Toastify({
|
|
text: message,
|
|
duration: 3000,
|
|
gravity: "top",
|
|
position: "right",
|
|
backgroundColor: type === 'error' ? "#EF4444" :
|
|
type === 'success' ? "#10B981" : "#3B82F6",
|
|
}).showToast();
|
|
}
|
|
|
|
// Update command preview in sidebar
|
|
function updateCommandPreview() {
|
|
const fileName = appState.fileName || "your_file.pdf";
|
|
const dpi = dpiInput.value;
|
|
const xFactor = xFactorInput.value;
|
|
const yFactor = yFactorInput.value;
|
|
const autoAdjust = autoAdjustCheck.checked ? " --adjust" : "";
|
|
|
|
commandPreview.textContent = `python analyzer.py --image "${fileName}" --page ${appState.currentPage} --dpi ${dpi} &&
|
|
python fix_scaling.py --doctags output.doctags.txt --output fixed_output.doctags.txt --x-factor ${xFactor} --y-factor ${yFactor} &&
|
|
python visualizer.py --doctags fixed_output.doctags.txt --pdf "${fileName}" --page ${appState.currentPage}${autoAdjust} --show`;
|
|
}
|
|
|
|
// Analyze current page
|
|
async function analyzeCurrentPage() {
|
|
if (!appState.pdf) return;
|
|
|
|
setLoading(true, 'Starting analysis process...');
|
|
|
|
// Simulate the multi-step analysis process
|
|
// In a production environment, this would make actual API calls to the backend Python scripts
|
|
|
|
await simulateProcessingStep('Converting PDF to image...', 1000);
|
|
await simulateProcessingStep('Generating DocTags...', 2000);
|
|
await simulateProcessingStep('Fixing scaling factors...', 1000);
|
|
await simulateProcessingStep('Creating visualization...', 1000);
|
|
|
|
// Generate mock results
|
|
generateMockResults();
|
|
|
|
// Update UI with results
|
|
displayResults();
|
|
|
|
setLoading(false, 'Analysis complete!');
|
|
showToast('Page analyzed successfully!', 'success');
|
|
}
|
|
|
|
// Simulate processing step with delay
|
|
function simulateProcessingStep(message, delay) {
|
|
return new Promise(resolve => {
|
|
setLoading(true, message);
|
|
setTimeout(() => resolve(), delay);
|
|
});
|
|
}
|
|
|
|
// Generate mock analysis results
|
|
function generateMockResults() {
|
|
appState.isAnalyzed = true;
|
|
|
|
// Sample zones that might be detected in a PDF
|
|
appState.zones = [
|
|
{
|
|
type: 'section_header_level_1',
|
|
x1: 50,
|
|
y1: 50,
|
|
x2: pdfCanvas.width - 100,
|
|
y2: 100,
|
|
content: 'Document Title'
|
|
},
|
|
{
|
|
type: 'text',
|
|
x1: 50,
|
|
y1: 120,
|
|
x2: pdfCanvas.width - 100,
|
|
y2: 200,
|
|
content: 'This is a sample paragraph of text that demonstrates the structure detection capabilities. The analyzer identifies different elements in the document and applies appropriate tags.'
|
|
},
|
|
{
|
|
type: 'picture',
|
|
x1: 150,
|
|
y1: 220,
|
|
x2: pdfCanvas.width - 150,
|
|
y2: 400,
|
|
content: 'Image content'
|
|
},
|
|
{
|
|
type: 'text',
|
|
x1: 50,
|
|
y1: 420,
|
|
x2: pdfCanvas.width - 100,
|
|
y2: 500,
|
|
content: 'Additional text paragraph that follows the image. This would typically describe or elaborate on the image content.'
|
|
},
|
|
{
|
|
type: 'table',
|
|
x1: 100,
|
|
y1: 520,
|
|
x2: pdfCanvas.width - 100,
|
|
y2: 650,
|
|
content: 'Table data'
|
|
},
|
|
{
|
|
type: 'page_footer',
|
|
x1: 250,
|
|
y1: pdfCanvas.height - 50,
|
|
x2: pdfCanvas.width - 250,
|
|
y2: pdfCanvas.height - 20,
|
|
content: `Page ${appState.currentPage}`
|
|
}
|
|
];
|
|
|
|
// In a real implementation, you would process the returned DocTags data
|
|
// and convert it into zone objects with coordinates
|
|
}
|
|
|
|
// Display analysis results
|
|
function displayResults() {
|
|
// Show results section
|
|
resultsSection.style.display = 'block';
|
|
exportBtn.disabled = false;
|
|
|
|
// Render zones on the PDF
|
|
renderZones();
|
|
|
|
// Build legend
|
|
buildLegend();
|
|
|
|
// Build zone list
|
|
buildZoneList();
|
|
|
|
// Update command preview with current page
|
|
updateCommandPreview();
|
|
}
|
|
|
|
// Render zones on top of the PDF
|
|
function renderZones() {
|
|
zoneOverlays.innerHTML = ''; // Clear existing overlays
|
|
|
|
if (!showZonesCheck.checked) return;
|
|
|
|
appState.zones.forEach((zone, index) => {
|
|
const zoneElement = document.createElement('div');
|
|
zoneElement.className = 'zone-overlay';
|
|
|
|
const zoneType = zone.type;
|
|
const color = zoneColors[zoneType] || zoneColors.default;
|
|
|
|
zoneElement.style.left = `${zone.x1}px`;
|
|
zoneElement.style.top = `${zone.y1}px`;
|
|
zoneElement.style.width = `${zone.x2 - zone.x1}px`;
|
|
zoneElement.style.height = `${zone.y2 - zone.y1}px`;
|
|
zoneElement.style.borderColor = color;
|
|
zoneElement.style.backgroundColor = transparentCheck.checked ?
|
|
'rgba(255, 255, 255, 0.1)' : 'rgba(255, 255, 255, 0.4)';
|
|
|
|
if (showLabelsCheck.checked) {
|
|
const labelElement = document.createElement('div');
|
|
labelElement.className = 'zone-overlay-label';
|
|
labelElement.style.borderRight = `2px solid ${color}`;
|
|
labelElement.style.borderBottom = `2px solid ${color}`;
|
|
labelElement.textContent = zoneType;
|
|
|
|
zoneElement.appendChild(labelElement);
|
|
}
|
|
|
|
zoneOverlays.appendChild(zoneElement);
|
|
});
|
|
}
|
|
|
|
// Build legend for zone types
|
|
function buildLegend() {
|
|
zoneLegend.innerHTML = '';
|
|
|
|
// Get unique zone types
|
|
const uniqueTypes = [...new Set(appState.zones.map(zone => zone.type))];
|
|
|
|
uniqueTypes.forEach(type => {
|
|
const color = zoneColors[type] || zoneColors.default;
|
|
|
|
const legendItem = document.createElement('div');
|
|
legendItem.className = 'legend-item';
|
|
|
|
const colorBox = document.createElement('div');
|
|
colorBox.className = 'legend-color';
|
|
colorBox.style.backgroundColor = color;
|
|
|
|
legendItem.appendChild(colorBox);
|
|
legendItem.appendChild(document.createTextNode(type));
|
|
|
|
zoneLegend.appendChild(legendItem);
|
|
});
|
|
}
|
|
|
|
// Build list of detected zones
|
|
function buildZoneList() {
|
|
zoneList.innerHTML = '';
|
|
|
|
appState.zones.forEach(zone => {
|
|
const zoneType = zone.type;
|
|
const color = zoneColors[zoneType] || zoneColors.default;
|
|
|
|
const zoneItem = document.createElement('div');
|
|
zoneItem.className = 'zone-item';
|
|
|
|
const zoneLabel = document.createElement('div');
|
|
zoneLabel.className = 'zone-label';
|
|
zoneLabel.style.backgroundColor = color;
|
|
zoneLabel.textContent = zoneType;
|
|
|
|
const zoneCoords = document.createElement('div');
|
|
zoneCoords.className = 'zone-coords';
|
|
zoneCoords.textContent = `Position: (${zone.x1}, ${zone.y1}) - (${zone.x2}, ${zone.y2})`;
|
|
|
|
zoneItem.appendChild(zoneLabel);
|
|
zoneItem.appendChild(zoneCoords);
|
|
|
|
if (zone.content) {
|
|
const zoneContent = document.createElement('div');
|
|
zoneContent.className = 'zone-content';
|
|
zoneContent.textContent = zone.content;
|
|
zoneItem.appendChild(zoneContent);
|
|
}
|
|
|
|
zoneList.appendChild(zoneItem);
|
|
});
|
|
}
|
|
|
|
// Update zone visibility based on checkboxes
|
|
function updateZoneVisibility() {
|
|
renderZones();
|
|
}
|
|
|
|
// Find optimal scale factors
|
|
async function findOptimalScale() {
|
|
if (!appState.pdf) return;
|
|
|
|
setLoading(true, 'Finding optimal scale factors...');
|
|
|
|
await simulateProcessingStep('Analyzing page boundaries...', 1000);
|
|
await simulateProcessingStep('Testing different scaling combinations...', 2000);
|
|
await simulateProcessingStep('Evaluating results...', 1000);
|
|
|
|
// Simulate finding optimal values
|
|
const optimalX = (Math.random() * 0.5 + 0.5).toFixed(2); // Random between 0.5 and 1.0
|
|
const optimalY = (Math.random() * 0.5 + 0.5).toFixed(2);
|
|
|
|
xFactorInput.value = optimalX;
|
|
yFactorInput.value = optimalY;
|
|
|
|
updateCommandPreview();
|
|
|
|
setLoading(false, `Optimal scaling: X=${optimalX}, Y=${optimalY}`);
|
|
showToast('Optimal scale factors determined', 'success');
|
|
}
|
|
|
|
// Export results
|
|
function exportResults() {
|
|
if (!appState.isAnalyzed) return;
|
|
|
|
const exportOptions = [
|
|
{ name: 'HTML Visualization', ext: 'html' },
|
|
{ name: 'DocTags Raw Format', ext: 'txt' },
|
|
{ name: 'Markdown Content', ext: 'md' }
|
|
];
|
|
|
|
const optionsHTML = exportOptions.map((option, index) =>
|
|
`<div class="checkbox-group" style="margin-bottom: 0.5rem;">
|
|
<input type="checkbox" id="export${index}" checked>
|
|
<label for="export${index}">${option.name} (.${option.ext})</label>
|
|
</div>`
|
|
).join('');
|
|
|
|
// Create a modal-like dialog (in a real app.py, you'd use a proper modal)
|
|
const dialog = document.createElement('div');
|
|
dialog.style.position = 'fixed';
|
|
dialog.style.top = '0';
|
|
dialog.style.left = '0';
|
|
dialog.style.right = '0';
|
|
dialog.style.bottom = '0';
|
|
dialog.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
|
|
dialog.style.display = 'flex';
|
|
dialog.style.alignItems = 'center';
|
|
dialog.style.justifyContent = 'center';
|
|
dialog.style.zIndex = '1000';
|
|
|
|
dialog.innerHTML = `
|
|
<div style="background: white; padding: 1.5rem; border-radius: 0.5rem; max-width: 400px; width: 100%;">
|
|
<h3 style="margin-bottom: 1rem;">Export Options</h3>
|
|
${optionsHTML}
|
|
<div style="margin-top: 1rem; display: flex; justify-content: flex-end; gap: 0.5rem;">
|
|
<button id="cancelExport" class="btn-outline">Cancel</button>
|
|
<button id="confirmExport" class="btn-primary">Export</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
document.getElementById('cancelExport').addEventListener('click', () => {
|
|
document.body.removeChild(dialog);
|
|
});
|
|
|
|
document.getElementById('confirmExport').addEventListener('click', () => {
|
|
document.body.removeChild(dialog);
|
|
|
|
// Simulate export process
|
|
setLoading(true, 'Exporting files...');
|
|
|
|
setTimeout(() => {
|
|
setLoading(false, 'Files exported successfully');
|
|
showToast('Export complete! Files saved to your output directory.', 'success');
|
|
}, 1500);
|
|
});
|
|
}
|
|
|
|
// In a real implementation, connect to the Python backend
|
|
// This would involve sending HTTP requests to an API that runs the Python scripts
|
|
|
|
// Example of how you might connect to a backend API:
|
|
async function callBackendAPI(endpoint, data) {
|
|
try {
|
|
const response = await fetch(`/api/${endpoint}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Error: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('API call failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Initialize the application when the DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
</script>
|
|
</body>
|
|
</html> |