doccompare / index.html
Ultronprime's picture
Add 3 files
ab89543 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuCompare - Intelligent PDF Comparison with Qwen 3</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
<style>
:root {
--primary: #6e56cf;
--primary-light: #8b7adb;
--primary-dark: #4a3698;
--secondary: #2fb344;
--dark: #1e293b;
--light: #f8fafc;
}
body {
font-family: 'Inter', sans-serif;
background-color: #f1f5f9;
}
.gradient-bg {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
}
.dropzone {
border: 2px dashed #cbd5e1;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: var(--primary);
background-color: rgba(110, 86, 207, 0.05);
}
.progress-bar {
height: 6px;
border-radius: 3px;
background-color: #e2e8f0;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: var(--primary);
transition: width 0.3s ease;
}
.result-card {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.result-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.file-chip {
background-color: #f0ebff;
color: var(--primary-dark);
}
.rotate-icon {
transform: rotate(0deg);
transition: transform 0.3s ease;
}
.rotate-icon.open {
transform: rotate(180deg);
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
padding: 1rem;
font-family: 'Courier New', Courier, monospace;
}
.loader {
width: 24px;
height: 24px;
border: 3px solid rgba(110, 86, 207, 0.3);
border-radius: 50%;
border-top-color: var(--primary);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="min-h-screen flex flex-col">
<!-- Header -->
<header class="gradient-bg text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<h1 class="text-2xl font-bold">DocuCompare AI</h1>
</div>
<div class="hidden md:flex items-center space-x-4">
<span class="text-white text-sm bg-white bg-opacity-20 px-3 py-1 rounded-full">Qwen 3 on HF</span>
<a href="#" class="text-white hover:text-gray-200 transition">API Key</a>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-grow container mx-auto px-4 py-8">
<div class="max-w-5xl mx-auto">
<!-- API Key Input -->
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
<div class="p-6">
<h3 class="text-xl font-semibold text-slate-800 mb-2">Hugging Face API Token</h3>
<div class="flex items-center space-x-2">
<input type="password" id="apiKeyInput" placeholder="Enter your HF API token (hf_...)" class="flex-grow px-4 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
<button id="saveApiKey" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">Save</button>
</div>
<p class="text-sm text-slate-500 mt-2">Your token is stored locally in your browser only.</p>
</div>
</div>
<!-- Upload Section -->
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
<div class="p-6 border-b border-slate-100">
<h3 class="text-xl font-semibold text-slate-800">1. Upload Document Files</h3>
<p class="text-slate-500 mt-1">Upload 2 or more documents (PDF/TXT/DOCX) for Qwen 3 to compare</p>
</div>
<div class="p-6">
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer">
<div class="flex flex-col items-center justify-center space-y-3">
<div class="p-4 bg-indigo-50 rounded-full">
<i data-feather="upload-cloud" class="w-8 h-8 text-indigo-500"></i>
</div>
<h4 class="font-medium text-slate-700">Drag & drop your files here</h4>
<p class="text-sm text-slate-500">or click to browse files</p>
<input type="file" id="fileInput" class="hidden" accept=".pdf,.txt,.docx" multiple>
<button id="browseBtn" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">
Select Files
</button>
</div>
</div>
<div id="selectedFiles" class="mt-4 hidden">
<h5 class="font-medium text-slate-700 mb-2">Selected Files:</h5>
<ul id="fileList" class="space-y-2"></ul>
</div>
</div>
</div>
<!-- Processing Section -->
<div class="bg-white rounded-xl shadow-md overflow-hidden hidden" id="processingSection">
<div class="p-6 border-b border-slate-100">
<h3 class="text-xl font-semibold text-slate-800">2. Processing Documents with Qwen 3</h3>
<p class="text-slate-500 mt-1">Qwen 3 235B is analyzing your documents</p>
</div>
<div class="p-6">
<div class="mb-6">
<div class="flex justify-between items-center mb-1">
<span class="text-sm font-medium text-slate-700">Progress</span>
<span class="text-sm font-medium text-slate-500" id="progressText">0%</span>
</div>
<div class="progress-bar">
<div id="progressFill" class="progress-fill" style="width: 0%"></div>
</div>
</div>
<div id="statusLog" class="bg-slate-50 p-4 rounded-lg max-h-40 overflow-y-auto text-sm text-slate-600">
<div class="status-item flex items-start space-x-2 py-1">
<i data-feather="info" class="w-4 h-4 text-blue-500 mt-0.5"></i>
<span>Waiting to start processing...</span>
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div class="hidden" id="resultsSection">
<h3 class="text-xl font-semibold text-slate-800 mb-4">3. Qwen 3 Analysis Results</h3>
<div class="grid md:grid-cols-2 gap-6 mb-6">
<div class="result-card bg-white rounded-lg p-5 border border-slate-200 flex flex-col items-center text-center">
<div class="p-3 bg-green-50 rounded-full mb-3">
<i data-feather="git-compare" class="w-6 h-6 text-green-500"></i>
</div>
<h4 class="font-medium text-slate-800 mb-1">Comparison Report</h4>
<p class="text-sm text-slate-500 mb-3">Detailed differences between documents</p>
<button id="showComparisonBtn" class="px-3 py-1 text-sm bg-green-600 text-white rounded hover:bg-green-700 transition">
View
</button>
</div>
<div class="result-card bg-white rounded-lg p-5 border border-slate-200 flex flex-col items-center text-center">
<div class="p-3 bg-purple-50 rounded-full mb-3">
<i data-feather="check-circle" class="w-6 h-6 text-purple-500"></i>
</div>
<h4 class="font-medium text-slate-800 mb-1">Validation Insights</h4>
<p class="text-sm text-slate-500 mb-3">Accuracy and validation analysis</p>
<button id="showValidationBtn" class="px-3 py-1 text-sm bg-purple-600 text-white rounded hover:bg-purple-700 transition">
View
</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
<div id="comparisonResults" class="hidden p-6">
<div class="flex justify-between items-center mb-4">
<h4 class="text-lg font-semibold text-slate-800">Qwen 3 Comparison Report</h4>
<div class="flex space-x-2">
<button id="copyComparisonBtn" class="flex items-center space-x-1 px-3 py-1 text-sm bg-slate-100 text-slate-600 rounded hover:bg-slate-200 transition">
<i data-feather="copy" class="w-4 h-4"></i>
<span>Copy</span>
</button>
<button id="downloadComparisonBtn" class="flex items-center space-x-1 px-3 py-1 text-sm bg-slate-100 text-slate-600 rounded hover:bg-slate-200 transition">
<i data-feather="download" class="w-4 h-4"></i>
<span>Download</span>
</button>
</div>
</div>
<div id="comparisonOutput" class="whitespace-pre-wrap text-sm text-slate-700"></div>
</div>
<div id="validationResults" class="hidden p-6">
<div class="flex justify-between items-center mb-4">
<h4 class="text-lg font-semibold text-slate-800">Qwen 3 Validation Insights</h4>
<div class="flex space-x-2">
<button id="copyValidationBtn" class="flex items-center space-x-1 px-3 py-1 text-sm bg-slate-100 text-slate-600 rounded hover:bg-slate-200 transition">
<i data-feather="copy" class="w-4 h-4"></i>
<span>Copy</span>
</button>
<button id="downloadValidationBtn" class="flex items-center space-x-1 px-3 py-1 text-sm bg-slate-100 text-slate-600 rounded hover:bg-slate-200 transition">
<i data-feather="download" class="w-4 h-4"></i>
<span>Download</span>
</button>
</div>
</div>
<div id="validationOutput" class="whitespace-pre-wrap text-sm text-slate-700"></div>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-white border-t border-slate-200 py-6">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center space-x-2 mb-4 md:mb-0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span class="font-medium text-slate-800">DocuCompare AI</span>
</div>
<div class="text-sm text-slate-500 text-center md:text-right">
<p>Powered by Qwen 3 via Hugging Face Endpoints</p>
</div>
</div>
</div>
</footer>
</div>
<script>
// Initialize feather icons
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
// System prompts for Qwen 3
const EXTRACTION_PROMPT = `You are a professional document analyst. Extract the following from the provided documents:
1. Document metadata (parties, dates, references)
2. Quantitative data (line items, amounts, dates)
3. Key terms and conditions
4. Any special provisions`;
const COMPARISON_PROMPT = `Compare these document extracts and highlight:
1. Major differences in terms and pricing
2. Variances in scope/specifications
3. Advantages/disadvantages of each document
4. Any red flags or concerns
Format as detailed markdown with tables where appropriate`;
const VALIDATION_PROMPT = `Validate this comparison report:
1. Check for accuracy against original docs
2. Identify missing comparisons
3. Note any possible misinterpretations
4. Suggest additional areas that need review`;
// DOM Elements
const apiKeyInput = document.getElementById('apiKeyInput');
const saveApiKey = document.getElementById('saveApiKey');
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const browseBtn = document.getElementById('browseBtn');
const selectedFiles = document.getElementById('selectedFiles');
const fileList = document.getElementById('fileList');
const processingSection = document.getElementById('processingSection');
const resultsSection = document.getElementById('resultsSection');
const progressText = document.getElementById('progressText');
const progressFill = document.getElementById('progressFill');
const statusLog = document.getElementById('statusLog');
const comparisonResults = document.getElementById('comparisonResults');
const validationResults = document.getElementById('validationResults');
const comparisonOutput = document.getElementById('comparisonOutput');
const validationOutput = document.getElementById('validationOutput');
// Check for saved API key
const savedApiKey = localStorage.getItem('hfApiToken');
if (savedApiKey) {
apiKeyInput.value = savedApiKey;
}
// Event Listeners
saveApiKey.addEventListener('click', () => {
const key = apiKeyInput.value.trim();
if (key && key.startsWith('hf_')) {
localStorage.setItem('hfApiToken', key);
addStatusLog("HF API token saved locally in your browser.", "success");
} else {
addStatusLog("Please enter a valid HF token (starting with hf_)", "error");
apiKeyInput.focus();
}
});
browseBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect);
// Drag and drop handlers
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
fileInput.files = e.dataTransfer.files;
handleFileSelect();
});
// Result navigation buttons
document.getElementById('showComparisonBtn').addEventListener('click', () => {
comparisonResults.classList.remove('hidden');
validationResults.classList.add('hidden');
});
document.getElementById('showValidationBtn').addEventListener('click', () => {
comparisonResults.classList.add('hidden');
validationResults.classList.remove('hidden');
});
// Copy/download buttons
document.getElementById('copyComparisonBtn').addEventListener('click', copyComparison);
document.getElementById('copyValidationBtn').addEventListener('click', copyValidation);
document.getElementById('downloadComparisonBtn').addEventListener('click', downloadComparison);
document.getElementById('downloadValidationBtn').addEventListener('click', downloadValidation);
// Functions
function handleFileSelect() {
const files = fileInput.files;
if (files.length < 2) {
addStatusLog('Please select at least 2 files for comparison.', 'error');
return;
}
// Show selected files
fileList.innerHTML = '';
Array.from(files).forEach(file => {
const li = document.createElement('li');
li.className = 'flex items-center justify-between bg-slate-50 px-3 py-2 rounded';
li.innerHTML = `
<div class="flex items-center space-x-3">
<i data-feather="file" class="w-4 h-4 text-slate-500"></i>
<span class="text-sm text-slate-700 truncate max-w-xs">${file.name}</span>
</div>
<span class="text-xs text-slate-500">${formatFileSize(file.size)}</span>
`;
fileList.appendChild(li);
});
selectedFiles.classList.remove('hidden');
// Start processing after a slight delay for UI to update
setTimeout(startProcessing, 1000);
feather.replace();
}
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];
}
function startProcessing() {
const apiKey = localStorage.getItem('hfApiToken');
if (!apiKey) {
addStatusLog('Please save your Hugging Face API token first.', 'error');
return;
}
processingSection.classList.remove('hidden');
addStatusLog('Starting document processing with Qwen 3...');
// Simulate processing with progress updates
simulateProcessing();
}
function simulateHFQwen3Call(prompt, content, callback) {
// In a real implementation, this would call the HF Inference API:
/*
fetch('https://router.huggingface.co/fireworks-ai/inference/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: [{
role: "user",
content: `${prompt}\n\n${content}`
}],
model: "accounts/fireworks/models/qwen3-235b-a22b",
stream: false
})
})
.then(response => response.json())
.then(data => callback(data.choices[0].message.content))
.catch(error => callback(`Error: ${error.message}`));
*/
// For demo purposes, simulate the API response
setTimeout(() => {
if (prompt === COMPARISON_PROMPT) {
callback(`## Comparison Report (Simulated)
### Key Findings
1. **Price Variation**: Document A shows consistently higher pricing (avg +18%) than Document B
2. **Scope Differences**: Document B includes additional services (maintenance, support) not in A
3. **Timeline**: Document A proposes 6 month timeline vs Document B's 4 month estimate
| Category | Document A | Document B | Difference |
|----------------|------------|------------|------------|
| Development | $28,500 | $24,000 | +18.75% |
| Testing | $9,200 | $7,500 | +22.67% |
| Maintenance | - | $3,000 | N/A |
**Recommendation**: Consider negotiating with Provider A to match Provider B's pricing or switch to Provider B for lower costs and additional services.`);
} else if (prompt === VALIDATION_PROMPT) {
callback(`## Validation Report (Simulated)
### Verification Points
✅ Pricing differences confirmed across all line items
✅ Document B does indeed include maintenance services
⚠️ Timeline estimates should be validated with both providers
### Additional Findings
1. Payment terms weren't compared (50% upfront in both)
2. Penalty clauses differ (5% vs 10% for delays)
3. Document A includes better IP protections
**Action Items**:
1. Verify actual delivery capacity for timeline claims
2. Compare quality guarantees between providers`);
} else {
callback(`Error: Unsupported prompt type`);
}
}, 2000);
}
function simulateProcessing() {
let progress = 0;
const steps = [
{text: "Uploading documents...", increment: 15},
{text: "Extracting content from files...", increment: 25},
{text: "Sending to Qwen 3 via HF endpoint...", increment: 30},
{text: "Analyzing document differences...", increment: 15},
{text: "Generating validation report...", increment: 10},
{text: "Finalizing results...", increment: 5}
];
let currentStep = 0;
const processInterval = setInterval(() => {
if (currentStep < steps.length) {
const step = steps[currentStep];
// Update progress
progress = Math.min(progress + step.increment, 100);
progressText.textContent = `${progress}%`;
progressFill.style.width = `${progress}%`;
// Add status log entry
addStatusLog(step.text);
// Simulate API calls during processing
if (currentStep === 2) {
const mockContent = `
Document A: Vendor Alpha Proposal
- Development: $28,500
- Testing: $9,200
- Timeline: 6 months
- Payment: 50% upfront
Document B: Supplier Beta Offer
- Development: $24,000
- Testing: $7,500
- Maintenance: $3,000
- Timeline: 4 months
- Payment: 50% upfront`;
simulateHFQwen3Call(COMPARISON_PROMPT, mockContent, (result) => {
comparisonOutput.textContent = result;
addStatusLog('Comparison report generated successfully', 'success');
});
simulateHFQwen3Call(VALIDATION_PROMPT, mockContent, (result) => {
validationOutput.textContent = result;
addStatusLog('Validation analysis complete', 'success');
});
}
currentStep++;
} else {
clearInterval(processInterval);
// Add completion status
addStatusLog("Analysis complete! View results below.", "success");
// Show results after a delay
setTimeout(showResults, 1000);
}
}, 1500);
}
function addStatusLog(message, type = "info") {
const statusItem = document.createElement('div');
statusItem.className = 'status-item flex items-start space-x-2 py-1';
let icon;
let textColor;
switch(type) {
case "success":
icon = 'check-circle';
textColor = 'text-green-500';
break;
case "error":
icon = 'alert-circle';
textColor = 'text-red-500';
break;
case "warning":
icon = 'alert-triangle';
textColor = 'text-yellow-500';
break;
default:
icon = 'info';
textColor = 'text-blue-500';
}
statusItem.innerHTML = `
<i data-feather="${icon}" class="w-4 h-4 ${textColor} mt-0.5"></i>
<span>${message}</span>
`;
statusLog.appendChild(statusItem);
statusLog.scrollTop = statusLog.scrollHeight;
feather.replace();
}
function showResults() {
processingSection.classList.add('hidden');
resultsSection.classList.remove('hidden');
// Show comparison by default
comparisonResults.classList.remove('hidden');
validationResults.classList.add('hidden');
}
// Clipboard functions
function copyComparison() {
navigator.clipboard.writeText(comparisonOutput.textContent);
showCopyFeedback('copyComparisonBtn');
}
function copyValidation() {
navigator.clipboard.writeText(validationOutput.textContent);
showCopyFeedback('copyValidationBtn');
}
function showCopyFeedback(buttonId) {
const button = document.getElementById(buttonId);
const icon = button.querySelector('i');
const text = button.querySelector('span');
// Change to indicate success
icon.setAttribute('data-feather', 'check');
feather.replace();
text.textContent = 'Copied';
// Reset after 2 seconds
setTimeout(() => {
icon.setAttribute('data-feather', 'copy');
feather.replace();
text.textContent = 'Copy';
}, 2000);
}
// Download functions
function downloadComparison() {
downloadFile('Qwen3_Comparison_Report.md', comparisonOutput.textContent);
}
function downloadValidation() {
downloadFile('Qwen3_Validation_Report.md', validationOutput.textContent);
}
function downloadFile(filename, content) {
const blob = new Blob([content], {type: 'text/markdown'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Ultronprime/doccompare" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>