document-engine / index.html
basudevm23's picture
Upload 5 files
a9b921f verified
Raw
History Blame Contribute Delete
17.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hybrid Multimodal Document Engine</title>
<style>
:root { --bg-main: #f8fafc; --panel-bg: #ffffff; --border-color: #e2e8f0; --text-dark: #1e293b; --accent-blue: #2563eb; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background-color: var(--bg-main); color: var(--text-dark); margin: 0; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
.grid { display: grid; grid-template-columns: 1fr; gap: 20px; margin-top: 20px; }
.panel { background: var(--panel-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
h2 { margin-top: 0; font-size: 1.25rem; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: 600; }
input[type="file"], select, input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; box-sizing: border-box; }
button { background-color: var(--accent-blue); color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: 600; }
button:hover { opacity: 0.9; }
button:disabled { background-color: #94a3b8; cursor: not-allowed; }
.btn-secondary { background-color: #64748b; padding: 8px 15px; font-size: 0.85rem; margin-top: 5px; }
.btn-warning { background-color: #f59e0b; color: white; }
table { width: 100%; border-collapse: collapse; margin-top: 15px; font-size: 0.95rem; }
th, td { text-align: left; padding: 10px; border-bottom: 1px solid var(--border-color); }
th { background-color: #f1f5f9; }
.progress-container { margin-top: 20px; padding: 15px; border: 1px solid #cbd5e1; border-radius: 6px; background-color: #f8fafc; display: none; }
.progress-bar-bg { width: 100%; height: 12px; background-color: #e2e8f0; border-radius: 6px; overflow: hidden; margin: 10px 0; }
.progress-bar-fill { height: 100%; background-color: #22c55e; width: 0%; transition: width 0.5s linear; }
.status-text { font-weight: bold; color: #334155; }
.time-remaining { font-size: 0.9rem; color: #64748b; font-style: italic; }
.extracted-value-highlight { color: #16a34a; font-weight: 700; }
.error-highlight { color: #ef4444; font-weight: 700; }
.chat-box { height: 300px; overflow-y: auto; border: 1px solid var(--border-color); padding: 10px; border-radius: 4px; margin-bottom: 10px; background: #fafafa; font-size: 0.95rem; line-height: 1.5; }
.chat-input-area { display: flex; gap: 10px; }
</style>
</head>
<body>
<div class="container">
<h1>Multilingual Document Translator & Data Extractor</h1>
<div class="form-group">
<label> (Powered by Gemini for cloud-based AI processing. Document data is securely transmitted for analysis.)<span style="font-weight:normal; color:#64748b;"></span></label>
</div>
<div class="grid">
<div class="panel">
<h2>Upload Your Documents</h2>
<form id="uploadForm">
<div class="form-group">
<label>Document to Process (PDF)</label>
<input type="file" id="docFile" accept=".pdf" required>
</div>
<div class="form-group">
<label>Excel Formatting Sheet (.xlsx)</label>
<input type="file" id="excelFile" accept=".xlsx" required>
</div>
<div class="form-group">
<label>Google Gemini API Key <span style="font-weight:normal; color:#64748b;"></span></label>
<input type="password" id="apiKey" placeholder="AIzaSy..." autocomplete="off">
<button type="button" id="syncModelsBtn" class="btn-secondary">🔄 Sync Available Models</button>
</div>
<div class="form-group">
<label>Processing Mode</label>
<select id="modelSelection">
<!-- <option value="gemini-1.5-flash">Cloud: Gemini 1.5 Flash (Uses Vision RAG)</option>
<option value="qwen2.5:14b">Local: Qwen 2.5 14B (Strict Text Privacy)</option> -->
</select>
</div>
<button type="submit" id="submitBtn">Start Processing</button>
<button type="button" id="retryBtn" class="btn-warning" style="display:none; margin-top:10px;">⚠️ Rate Limit / Errors Detected. Switch Models and Retry Failed Fields</button>
</form>
<div class="progress-container" id="progressArea">
<div class="status-text" id="statusText">Initializing System...</div>
<div class="progress-bar-bg">
<div class="progress-bar-fill" id="progressBar"></div>
</div>
<div class="time-remaining" id="timeRemaining">Calculating estimated completion time...</div>
</div>
<div id="downloadArea" style="margin-top: 15px; display: none;">
<button id="downloadBtn" style="background-color: #16a34a;">Download Clean Formatted Excel Sheet</button>
</div>
</div>
<div class="panel">
<h2>Processing Progress</h2>
<div style="overflow-x: auto;">
<table id="logsTable">
<thead>
<tr>
<th>Spreadsheet</th>
<th>Field</th>
<th>Extracted Information (English)</th>
<th>Found on Page(s)</th>
<th>Processing Time</th>
</tr>
</thead>
<tbody>
<tr><td colspan="5" style="text-align: center; color: #64748b;">Upload your files and click "Start Processing".</td></tr>
</tbody>
</table>
</div>
</div>
<div class="panel">
<h2>Ask About the Document</h2>
<div class="chat-box" id="chatBox">
<p style="color: #64748b; font-style: italic;">Ask questions about the document or extracted information...</p>
</div>
<div class="chat-input-area">
<input type="text" id="chatInput" placeholder="Example: Summarize the key information in this document.">
<button id="sendChatBtn">Ask AI</button>
</div>
</div>
</div>
</div>
<script>
let generatedFilePath = "";
let currentVisualProgress = 0;
let targetVisualProgress = 0;
let progressInterval = null;
function startContinuousProgress() {
if(progressInterval) clearInterval(progressInterval);
progressInterval = setInterval(() => {
if(currentVisualProgress < targetVisualProgress - 0.5) {
currentVisualProgress += 0.16;
document.getElementById('progressBar').style.width = currentVisualProgress + '%';
}
}, 1000);
}
document.getElementById('syncModelsBtn').addEventListener('click', async () => {
const apiKey = document.getElementById('apiKey').value.trim();
const selectEl = document.getElementById('modelSelection');
const btn = document.getElementById('syncModelsBtn');
btn.innerText = "⏳ Syncing..."; btn.disabled = true;
selectEl.innerHTML = '<option>Fetching available models...</option>';
try {
const response = await fetch(`/api/models?api_key=${encodeURIComponent(apiKey)}`);
const data = await response.json();
selectEl.innerHTML = '';
data.models.forEach(m => {
const opt = document.createElement('option');
opt.value = m.value; opt.textContent = m.label;
selectEl.appendChild(opt);
});
const flashOption = Array.from(selectEl.options).find(opt => opt.value === 'gemini-1.5-flash');
if (flashOption) flashOption.selected = true;
} catch (err) {
selectEl.innerHTML = '<option value="qwen2.5:14b">Local: Qwen 2.5 14B</option>';
} finally {
btn.innerText = "🔄 Sync Available Models"; btn.disabled = false;
}
});
async function executePipeline(retryFailed = false) {
const submitBtn = document.getElementById('submitBtn');
const retryBtn = document.getElementById('retryBtn');
const statusText = document.getElementById('statusText');
const timeRemainingEl = document.getElementById('timeRemaining');
submitBtn.disabled = true;
retryBtn.style.display = 'none';
document.getElementById('progressArea').style.display = 'block';
document.getElementById('downloadArea').style.display = 'none';
if(!retryFailed) {
currentVisualProgress = 0; targetVisualProgress = 0;
document.getElementById('logsTable').querySelector('tbody').innerHTML = "";
}
startContinuousProgress();
const formData = new FormData();
formData.append('doc_file', document.getElementById('docFile').files[0]);
formData.append('excel_file', document.getElementById('excelFile').files[0]);
formData.append('model_selection', document.getElementById('modelSelection').value);
formData.append('api_key', document.getElementById('apiKey').value);
formData.append('retry_failed_only', retryFailed ? "true" : "false");
let latencyHistory = [];
try {
const response = await fetch('/api/process', { method: 'POST', body: formData });
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (let line of lines) {
if (!line.trim()) continue;
try {
const data = JSON.parse(line);
if (data.status === "phase") {
statusText.innerText = data.message;
if(data.phase === 1) targetVisualProgress = 25;
else if(data.phase === 2) targetVisualProgress = 50;
else if(data.phase === 3) targetVisualProgress = 75;
else if(data.phase === 4) targetVisualProgress = 80;
currentVisualProgress = targetVisualProgress;
document.getElementById('progressBar').style.width = currentVisualProgress + '%';
}
else if (data.status === "extracted") {
statusText.innerText = `Extracting ${data.current} of ${data.total} fields...`;
targetVisualProgress = 80 + ((data.current / data.total) * 20);
currentVisualProgress = targetVisualProgress;
document.getElementById('progressBar').style.width = currentVisualProgress + '%';
const isError = data.value.includes("Error") || data.value.includes("Not Found");
const valClass = isError ? "error-highlight" : "extracted-value-highlight";
const tr = document.createElement('tr');
tr.innerHTML = `<td>${data.sheet}</td><td>${data.column}</td><td class="${valClass}">${data.value}</td><td>Pg: ${data.pages.join(', ')}</td><td>${data.latency}s</td>`;
document.getElementById('logsTable').querySelector('tbody').prepend(tr);
latencyHistory.push(data.latency);
const avgTime = latencyHistory.reduce((a, b) => a + b, 0) / latencyHistory.length;
const itemsLeft = data.total - data.current;
const isCloud = document.getElementById('modelSelection').value.startsWith("gemini");
const divisor = isCloud ? 8 : 1;
const secLeft = Math.round(itemsLeft * avgTime / divisor);
if (itemsLeft > 0) {
timeRemainingEl.innerText = `Estimated Time Remaining: ~${secLeft} seconds`;
}
}
else if (data.status === "rate_limit" || data.status === "done") {
clearInterval(progressInterval);
currentVisualProgress = 100;
document.getElementById('progressBar').style.width = '100%';
generatedFilePath = data.file_path;
document.getElementById('downloadArea').style.display = 'block';
if(data.status === "rate_limit") {
statusText.innerText = "⚠️ Pipeline paused due to API Limits or Failures.";
retryBtn.style.display = 'block';
} else {
statusText.innerText = "✅ Extraction Successfully Completed!";
timeRemainingEl.innerText = "All parallel agents resolved.";
}
}
} catch (e) { console.error("Parse err", line); }
}
}
} catch (err) {
alert("Network communication failure: " + err.message);
clearInterval(progressInterval);
} finally {
submitBtn.disabled = false;
}
}
document.getElementById('uploadForm').addEventListener('submit', (e) => { e.preventDefault(); executePipeline(false); });
document.getElementById('retryBtn').addEventListener('click', () => { executePipeline(true); });
document.getElementById('downloadBtn').addEventListener('click', () => {
if(generatedFilePath) window.location.href = `/api/download?path=${encodeURIComponent(generatedFilePath)}`;
});
document.getElementById('sendChatBtn').addEventListener('click', async () => {
const input = document.getElementById('chatInput');
const chatBox = document.getElementById('chatBox');
const modelSelection = document.getElementById('modelSelection').value;
const apiKey = document.getElementById('apiKey').value;
const text = input.value.trim();
if(!text) return;
chatBox.innerHTML += `<p><strong>User:</strong> ${text}</p>`;
input.value = "";
chatBox.scrollTop = chatBox.scrollHeight;
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: text, model_selection: modelSelection, api_key: apiKey })
});
const data = await response.json();
if (!response.ok) {
chatBox.innerHTML += `
<div style="background-color: #fef2f2; border-left: 4px solid #ef4444; padding: 10px; margin: 10px 0; border-radius: 4px;">
<p style="color: #b91c1c; margin: 0;"><strong>⚠️ System Alert:</strong> ${data.detail}</p>
</div>`;
} else {
let sourceInfo = data.citations_pages.length > 0 ? `<br><small style="color: #2563eb; font-size: 0.85rem;">[Sources Traced on Pages: ${data.citations_pages.join(', ')}]</small>` : '';
chatBox.innerHTML += `<p><strong>Agent (${modelSelection}):</strong> ${data.response} ${sourceInfo}</p>`;
}
chatBox.scrollTop = chatBox.scrollHeight;
} catch (err) {
chatBox.innerHTML += `<p style="color: red;">Failed to communicate with API.</p>`;
}
});
</script>
</body>
</html>