propertyverification / templates /visualindex.html
sksameermujahid's picture
Upload 34 files
9860c76 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Property Visual Search - AI-Powered Image Recognition</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
color: white;
}
.header h1 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
font-weight: 300;
}
.main-content {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
margin-bottom: 30px;
}
.upload-section {
padding: 40px;
text-align: center;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.upload-area {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 60px 20px;
background: white;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 30px;
}
.upload-area:hover {
border-color: #764ba2;
background: #f8f9ff;
transform: translateY(-2px);
}
.upload-area.dragover {
border-color: #764ba2;
background: #f0f2ff;
transform: scale(1.02);
}
.upload-icon {
font-size: 4rem;
color: #667eea;
margin-bottom: 20px;
}
.upload-text {
font-size: 1.2rem;
color: #666;
margin-bottom: 10px;
}
.upload-subtext {
font-size: 0.9rem;
color: #999;
}
.file-input {
display: none;
}
.upload-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 30px;
border-radius: 50px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 20px;
}
.upload-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.upload-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.status-section {
padding: 20px 40px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9rem;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #dc3545;
}
.status-dot.ready {
background: #28a745;
}
.status-dot.loading {
background: #ffc107;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.results-section {
padding: 40px;
display: none;
}
.results-header {
text-align: center;
margin-bottom: 30px;
}
.results-header h2 {
color: #333;
font-size: 1.8rem;
margin-bottom: 10px;
}
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 30px;
}
.result-card {
background: white;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
overflow: hidden;
transition: all 0.3s ease;
}
.result-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(0,0,0,0.15);
}
.result-image {
width: 100%;
height: 200px;
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 0.9rem;
}
.result-image img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0;
}
.result-card img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 0;
}
.result-info {
padding: 20px;
}
.result-title {
font-weight: 600;
color: #333;
margin-bottom: 10px;
}
.result-details {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.similarity-score {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 5px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.property-id {
color: #666;
font-size: 0.9rem;
}
.loading-spinner {
display: none;
text-align: center;
padding: 40px;
}
.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); }
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 10px;
margin: 20px 0;
display: none;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 10px;
margin: 20px 0;
display: none;
}
.preview-image {
max-width: 100%;
max-height: 300px;
border-radius: 10px;
margin: 20px 0;
display: none;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 30px;
}
.feature-card {
background: white;
padding: 30px;
border-radius: 15px;
text-align: center;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.feature-icon {
font-size: 2.5rem;
color: #667eea;
margin-bottom: 20px;
}
.feature-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.feature-description {
color: #666;
line-height: 1.6;
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.upload-section {
padding: 20px;
}
.upload-area {
padding: 40px 20px;
}
.results-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-search"></i> Property Visual Search</h1>
<p>AI-Powered Image Recognition for Real Estate</p>
</div>
<div class="main-content">
<div class="upload-section">
<div class="upload-area" id="uploadArea">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<div class="upload-text">Drag & Drop your property image here</div>
<div class="upload-subtext">or click to browse files (JPG, PNG, GIF)</div>
<input type="file" id="fileInput" class="file-input" accept="image/*">
<button class="upload-btn" id="uploadBtn" onclick="document.getElementById('fileInput').click()">
<i class="fas fa-upload"></i> Choose Image
</button>
</div>
<img id="previewImage" class="preview-image" alt="Preview">
<div class="error-message" id="errorMessage"></div>
<div class="success-message" id="successMessage"></div>
</div>
<div class="status-section">
<div class="status-indicator">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Initializing visual search system...</span>
</div>
</div>
<div class="loading-spinner" id="loadingSpinner">
<div class="spinner"></div>
<p>Analyzing your image and finding similar properties...</p>
</div>
<div class="results-section" id="resultsSection">
<div class="results-header">
<h2>Similar Properties Found</h2>
<p>Based on visual similarity analysis</p>
</div>
<div class="results-grid" id="resultsGrid"></div>
</div>
</div>
<div class="features">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-brain"></i>
</div>
<div class="feature-title">AI-Powered</div>
<div class="feature-description">
Advanced machine learning algorithms analyze visual patterns and architectural features
</div>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-bolt"></i>
</div>
<div class="feature-title">Lightning Fast</div>
<div class="feature-description">
Get instant results with our optimized image processing and similarity matching
</div>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="feature-title">Secure & Private</div>
<div class="feature-description">
Your uploaded images are processed securely and never stored permanently
</div>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-chart-line"></i>
</div>
<div class="feature-title">Smart Matching</div>
<div class="feature-description">
Find properties with similar architectural styles, layouts, and visual characteristics
</div>
</div>
</div>
</div>
<script>
let isSystemReady = false;
// Check system status on page load
checkSystemStatus();
// Upload area functionality
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const previewImage = document.getElementById('previewImage');
const uploadBtn = document.getElementById('uploadBtn');
const loadingSpinner = document.getElementById('loadingSpinner');
const resultsSection = document.getElementById('resultsSection');
const resultsGrid = document.getElementById('resultsGrid');
const errorMessage = document.getElementById('errorMessage');
const successMessage = document.getElementById('successMessage');
// Drag and drop functionality
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
function handleFile(file) {
if (!isSystemReady) {
showError('Visual search system is still initializing. Please wait a moment and try again.');
return;
}
if (!file.type.startsWith('image/')) {
showError('Please select a valid image file.');
return;
}
// Show preview
const reader = new FileReader();
reader.onload = (e) => {
previewImage.src = e.target.result;
previewImage.style.display = 'block';
};
reader.readAsDataURL(file);
// Upload and search
uploadAndSearch(file);
}
function uploadAndSearch(file) {
const formData = new FormData();
formData.append('file', file);
// Show loading
loadingSpinner.style.display = 'block';
resultsSection.style.display = 'none';
hideMessages();
fetch('/search', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
loadingSpinner.style.display = 'none';
if (data.error) {
showError(data.error);
} else if (data.message) {
showSuccess(data.message);
} else if (data.results) {
displayResults(data.results);
}
})
.catch(error => {
loadingSpinner.style.display = 'none';
showError('An error occurred while processing your image. Please try again.');
console.error('Error:', error);
});
}
function displayResults(results) {
resultsSection.style.display = 'block';
resultsGrid.innerHTML = '';
results.forEach((result, index) => {
const resultCard = document.createElement('div');
resultCard.className = 'result-card';
// Create image element with actual property image or fallback
let imageHtml = '';
if (result.image_path) {
imageHtml = `<img src="${result.image_path}" alt="Property ${index + 1}" style="width: 100%; height: 200px; object-fit: cover;" onerror="this.parentElement.innerHTML='<div class=\\'result-image\\'><i class=\\'fas fa-home\\' style=\\'font-size: 3rem; color: #ddd;\\'></i></div>'">`;
} else {
imageHtml = `<div class="result-image">
<i class="fas fa-home" style="font-size: 3rem; color: #ddd;"></i>
</div>`;
}
resultCard.innerHTML = `
${imageHtml}
<div class="result-info">
<div class="result-title">Property ${index + 1}</div>
<div class="result-details">
<span class="similarity-score">${result.similarity_score}</span>
<span class="property-id">ID: ${result.property_id}</span>
</div>
<div style="font-size: 0.8rem; color: #999;">
Distance: ${result.distance}
</div>
</div>
`;
resultsGrid.appendChild(resultCard);
});
showSuccess(`Found ${results.length} similar properties!`);
}
function checkSystemStatus() {
fetch('/status')
.then(response => response.json())
.then(data => {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
if (data.model_loaded && data.collection_ready) {
statusDot.className = 'status-dot ready';
statusText.textContent = `System ready! ${data.total_images} properties indexed`;
isSystemReady = true;
} else {
statusDot.className = 'status-dot loading';
statusText.textContent = 'Initializing system... Please wait';
isSystemReady = false;
// Check again in 5 seconds
setTimeout(checkSystemStatus, 5000);
}
})
.catch(error => {
console.error('Error checking status:', error);
setTimeout(checkSystemStatus, 5000);
});
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
successMessage.style.display = 'none';
}
function showSuccess(message) {
successMessage.textContent = message;
successMessage.style.display = 'block';
errorMessage.style.display = 'none';
}
function hideMessages() {
errorMessage.style.display = 'none';
successMessage.style.display = 'none';
}
</script>
</body>
</html>