InklyAI / templates /index.html
pravinai's picture
Upload folder using huggingface_hub
8eab354 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>InklyAI - Signature Verification</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
font-weight: 300;
}
.header p {
font-size: 1.2em;
opacity: 0.9;
}
.main-content {
padding: 40px;
}
.upload-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
margin-bottom: 40px;
}
.upload-box {
border: 3px dashed #ddd;
border-radius: 15px;
padding: 40px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
background: #fafafa;
}
.upload-box:hover {
border-color: #667eea;
background: #f0f4ff;
}
.upload-box.dragover {
border-color: #667eea;
background: #e8f2ff;
transform: scale(1.02);
}
.upload-icon {
font-size: 3em;
color: #667eea;
margin-bottom: 20px;
}
.upload-text {
font-size: 1.1em;
color: #666;
margin-bottom: 20px;
}
.file-input {
display: none;
}
.upload-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
cursor: pointer;
font-size: 1em;
transition: transform 0.2s ease;
}
.upload-btn:hover {
transform: translateY(-2px);
}
.preview-container {
margin-top: 20px;
text-align: center;
}
.preview-image {
max-width: 200px;
max-height: 200px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
margin-bottom: 10px;
}
.file-info {
font-size: 0.9em;
color: #666;
}
.verification-section {
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
}
.verification-title {
font-size: 1.5em;
margin-bottom: 20px;
color: #333;
text-align: center;
}
.verify-btn {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
padding: 15px 40px;
border-radius: 25px;
cursor: pointer;
font-size: 1.2em;
width: 100%;
margin-bottom: 20px;
transition: transform 0.2s ease;
}
.verify-btn:hover {
transform: translateY(-2px);
}
.verify-btn:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.result-section {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
display: none;
}
.result-title {
font-size: 1.3em;
margin-bottom: 20px;
text-align: center;
}
.result-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
align-items: center;
}
.result-image {
max-width: 100%;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.result-details {
padding: 20px;
}
.result-item {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
padding: 10px;
background: #f8f9fa;
border-radius: 8px;
}
.result-label {
font-weight: 600;
color: #333;
}
.result-value {
color: #666;
}
.similarity-score {
font-size: 1.5em;
font-weight: bold;
color: #28a745;
}
.verification-status {
font-size: 1.2em;
font-weight: bold;
padding: 10px 20px;
border-radius: 20px;
text-align: center;
}
.verified {
background: #d4edda;
color: #155724;
}
.not-verified {
background: #f8d7da;
color: #721c24;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.agent-section {
background: #e8f2ff;
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
}
.agent-title {
font-size: 1.3em;
margin-bottom: 20px;
color: #333;
text-align: center;
}
.agent-select {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 1em;
margin-bottom: 20px;
}
.stats-section {
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
margin-top: 30px;
}
.stats-title {
font-size: 1.3em;
margin-bottom: 20px;
color: #333;
text-align: center;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.stat-value {
font-size: 2em;
font-weight: bold;
color: #667eea;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 0.9em;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
display: none;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
display: none;
}
@media (max-width: 768px) {
.upload-section {
grid-template-columns: 1fr;
gap: 20px;
}
.result-content {
grid-template-columns: 1fr;
}
.main-content {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>InklyAI</h1>
<p>Advanced E-Signature Verification System</p>
</div>
<div class="main-content">
<!-- Error and Success Messages -->
<div id="errorMessage" class="error-message"></div>
<div id="successMessage" class="success-message"></div>
<!-- Agent Selection -->
<div class="agent-section">
<h2 class="agent-title">Select Agent for Verification</h2>
<select id="agentSelect" class="agent-select">
<option value="">Select an agent...</option>
</select>
</div>
<!-- Upload Section -->
<div class="upload-section">
<div class="upload-box" id="uploadBox1">
<div class="upload-icon">📝</div>
<div class="upload-text">Upload Reference Signature</div>
<button class="upload-btn" onclick="document.getElementById('file1').click()">
Choose File
</button>
<input type="file" id="file1" class="file-input" accept="image/*" onchange="handleFileSelect(event, 1)">
<div class="preview-container" id="preview1"></div>
</div>
<div class="upload-box" id="uploadBox2">
<div class="upload-icon">✍️</div>
<div class="upload-text">Upload Signature to Verify</div>
<button class="upload-btn" onclick="document.getElementById('file2').click()">
Choose File
</button>
<input type="file" id="file2" class="file-input" accept="image/*" onchange="handleFileSelect(event, 2)">
<div class="preview-container" id="preview2"></div>
</div>
</div>
<!-- Verification Section -->
<div class="verification-section">
<h2 class="verification-title">Signature Verification</h2>
<button id="verifyBtn" class="verify-btn" onclick="verifySignatures()">
Verify Signatures
</button>
<div class="loading" id="loading">
<div class="spinner"></div>
<div>Verifying signatures...</div>
</div>
</div>
<!-- Result Section -->
<div class="result-section" id="resultSection">
<h2 class="result-title">Verification Result</h2>
<div class="result-content">
<div>
<img id="resultImage1" class="result-image" alt="Reference Signature">
<div class="file-info" id="resultInfo1"></div>
</div>
<div class="result-details">
<div class="result-item">
<span class="result-label">Verification Status:</span>
<span id="verificationStatus" class="result-value verification-status"></span>
</div>
<div class="result-item">
<span class="result-label">Similarity Score:</span>
<span id="similarityScore" class="result-value similarity-score"></span>
</div>
<div class="result-item">
<span class="result-label">Confidence:</span>
<span id="confidence" class="result-value"></span>
</div>
<div class="result-item">
<span class="result-label">Verification ID:</span>
<span id="verificationId" class="result-value"></span>
</div>
<div class="result-item">
<span class="result-label">Timestamp:</span>
<span id="timestamp" class="result-value"></span>
</div>
</div>
</div>
</div>
<!-- Statistics Section -->
<div class="stats-section">
<h2 class="stats-title">Agent Statistics</h2>
<div class="stats-grid" id="statsGrid">
<!-- Stats will be loaded here -->
</div>
</div>
</div>
</div>
<script>
let selectedFiles = { file1: null, file2: null };
let selectedAgent = null;
// Load agents on page load
document.addEventListener('DOMContentLoaded', function() {
loadAgents();
loadStats();
});
// Drag and drop functionality
['uploadBox1', 'uploadBox2'].forEach((boxId, index) => {
const box = document.getElementById(boxId);
const fileInput = document.getElementById(`file${index + 1}`);
box.addEventListener('dragover', (e) => {
e.preventDefault();
box.classList.add('dragover');
});
box.addEventListener('dragleave', () => {
box.classList.remove('dragover');
});
box.addEventListener('drop', (e) => {
e.preventDefault();
box.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
fileInput.files = files;
handleFileSelect({ target: fileInput }, index + 1);
}
});
});
function handleFileSelect(event, fileNumber) {
const file = event.target.files[0];
if (file) {
selectedFiles[`file${fileNumber}`] = file;
displayPreview(file, fileNumber);
updateVerifyButton();
}
}
function displayPreview(file, fileNumber) {
const preview = document.getElementById(`preview${fileNumber}`);
const reader = new FileReader();
reader.onload = function(e) {
preview.innerHTML = `
<img src="${e.target.result}" class="preview-image" alt="Preview">
<div class="file-info">
<div>${file.name}</div>
<div>${(file.size / 1024).toFixed(1)} KB</div>
</div>
`;
};
reader.readAsDataURL(file);
}
function updateVerifyButton() {
const verifyBtn = document.getElementById('verifyBtn');
const hasFiles = selectedFiles.file1 && selectedFiles.file2;
const hasAgent = selectedAgent && selectedAgent !== '';
verifyBtn.disabled = !hasFiles || !hasAgent;
verifyBtn.textContent = hasFiles && hasAgent ? 'Verify Signatures' : 'Select Agent and Upload Both Signatures';
}
async function loadAgents() {
try {
const response = await fetch('/api/agents');
const data = await response.json();
const select = document.getElementById('agentSelect');
select.innerHTML = '<option value="">Select an agent...</option>';
data.agents.forEach(agent => {
const option = document.createElement('option');
option.value = agent.agent_id;
option.textContent = `${agent.agent_id} (${agent.is_active ? 'Active' : 'Inactive'})`;
select.appendChild(option);
});
select.addEventListener('change', function() {
selectedAgent = this.value;
updateVerifyButton();
});
} catch (error) {
showError('Failed to load agents: ' + error.message);
}
}
async function loadStats() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
const statsGrid = document.getElementById('statsGrid');
statsGrid.innerHTML = '';
Object.entries(data.stats).forEach(([agentId, stats]) => {
const statCard = document.createElement('div');
statCard.className = 'stat-card';
statCard.innerHTML = `
<div class="stat-value">${stats.total_verifications}</div>
<div class="stat-label">${agentId} Verifications</div>
<div style="margin-top: 10px; font-size: 0.8em; color: #666;">
Success Rate: ${(stats.success_rate * 100).toFixed(1)}%
</div>
`;
statsGrid.appendChild(statCard);
});
} catch (error) {
console.error('Failed to load stats:', error);
}
}
async function verifySignatures() {
if (!selectedFiles.file1 || !selectedFiles.file2 || !selectedAgent) {
showError('Please select an agent and upload both signatures');
return;
}
const loading = document.getElementById('loading');
const verifyBtn = document.getElementById('verifyBtn');
const resultSection = document.getElementById('resultSection');
// Show loading state
loading.style.display = 'block';
verifyBtn.disabled = true;
resultSection.style.display = 'none';
try {
const formData = new FormData();
formData.append('agent_id', selectedAgent);
formData.append('signature1', selectedFiles.file1);
formData.append('signature2', selectedFiles.file2);
const response = await fetch('/api/verify', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
displayResult(result);
showSuccess('Signatures verified successfully!');
loadStats(); // Refresh stats
} else {
showError('Verification failed: ' + result.error);
}
} catch (error) {
showError('Verification error: ' + error.message);
} finally {
loading.style.display = 'none';
verifyBtn.disabled = false;
}
}
function displayResult(result) {
const resultSection = document.getElementById('resultSection');
const verificationStatus = document.getElementById('verificationStatus');
const similarityScore = document.getElementById('similarityScore');
const confidence = document.getElementById('confidence');
const verificationId = document.getElementById('verificationId');
const timestamp = document.getElementById('timestamp');
// Update result values
verificationStatus.textContent = result.is_verified ? 'VERIFIED' : 'NOT VERIFIED';
verificationStatus.className = `result-value verification-status ${result.is_verified ? 'verified' : 'not-verified'}`;
similarityScore.textContent = (result.similarity_score * 100).toFixed(1) + '%';
confidence.textContent = (result.confidence * 100).toFixed(1) + '%';
verificationId.textContent = result.verification_id;
timestamp.textContent = new Date(result.timestamp).toLocaleString();
// Display images
const resultImage1 = document.getElementById('resultImage1');
const resultInfo1 = document.getElementById('resultInfo1');
if (selectedFiles.file1) {
const reader = new FileReader();
reader.onload = function(e) {
resultImage1.src = e.target.result;
};
reader.readAsDataURL(selectedFiles.file1);
resultInfo1.textContent = `Reference: ${selectedFiles.file1.name}`;
}
resultSection.style.display = 'block';
}
function showError(message) {
const errorDiv = document.getElementById('errorMessage');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}
function showSuccess(message) {
const successDiv = document.getElementById('successMessage');
successDiv.textContent = message;
successDiv.style.display = 'block';
setTimeout(() => {
successDiv.style.display = 'none';
}, 3000);
}
</script>
</body>
</html>