|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Cloudzy AI Photo Manager</title> |
|
|
<style> |
|
|
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } |
|
|
.upload-box { border: 2px dashed #ccc; padding: 20px; text-align: center; margin-bottom: 20px; } |
|
|
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; } |
|
|
.photo-card { border: 1px solid #eee; padding: 10px; border-radius: 8px; } |
|
|
.photo-card img { width: 100%; height: 150px; object-fit: cover; border-radius: 4px; } |
|
|
.tags { font-size: 0.8em; color: #666; } |
|
|
.meta { font-size: 0.7em; color: #888; margin-top: 5px; } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<h1>Cloudzy AI Challenge</h1> |
|
|
|
|
|
<div class="upload-box"> |
|
|
<h3>Upload Photo</h3> |
|
|
<input type="file" id="fileInput"> |
|
|
<button onclick="uploadPhoto()">Upload</button> |
|
|
<p id="uploadStatus"></p> |
|
|
</div> |
|
|
|
|
|
<div style="margin-bottom: 20px;"> |
|
|
<input type="text" id="searchInput" placeholder="Search (e.g., 'dog in grass' or 'happy person')..." style="width: 70%;"> |
|
|
<button onclick="searchPhotos()">Semantic Search</button> |
|
|
</div> |
|
|
|
|
|
<div id="gallery" class="gallery"></div> |
|
|
|
|
|
<script> |
|
|
async function uploadPhoto() { |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const status = document.getElementById('uploadStatus'); |
|
|
|
|
|
if (!fileInput.files[0]) return alert("Select a file!"); |
|
|
|
|
|
const formData = new FormData(); |
|
|
formData.append('file', fileInput.files[0]); |
|
|
|
|
|
status.innerText = "Uploading..."; |
|
|
|
|
|
try { |
|
|
const res = await fetch('/upload', { method: 'POST', body: formData }); |
|
|
const data = await res.json(); |
|
|
status.innerText = "Upload successful! ID: " + data.id + ". Processing AI..."; |
|
|
setTimeout(() => searchPhotos(""), 2000); |
|
|
} catch (e) { |
|
|
status.innerText = "Error uploading."; |
|
|
} |
|
|
} |
|
|
|
|
|
async function searchPhotos() { |
|
|
const query = document.getElementById('searchInput').value; |
|
|
const gallery = document.getElementById('gallery'); |
|
|
gallery.innerHTML = "Loading..."; |
|
|
|
|
|
let url = query ? `/search?q=${encodeURIComponent(query)}` : '/search?q=recent'; |
|
|
|
|
|
if (!query) return; |
|
|
|
|
|
const res = await fetch(url); |
|
|
const photos = await res.json(); |
|
|
|
|
|
gallery.innerHTML = ""; |
|
|
photos.forEach(photo => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'photo-card'; |
|
|
|
|
|
|
|
|
let faceInfo = ""; |
|
|
if (photo.smart_features && Array.isArray(photo.smart_features)) { |
|
|
faceInfo = `${photo.smart_features.length} Face(s) detected`; |
|
|
} else if (photo.smart_features && photo.smart_features.message) { |
|
|
faceInfo = photo.smart_features.message; |
|
|
} |
|
|
|
|
|
div.innerHTML = ` |
|
|
<img src="${photo.url}" alt="photo"> |
|
|
<p><strong>${photo.caption || "Processing..."}</strong></p> |
|
|
<div class="tags">${photo.tags.slice(0, 5).join(", ")}</div> |
|
|
<div class="meta">${faceInfo}</div> |
|
|
`; |
|
|
gallery.appendChild(div); |
|
|
}); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |