feat: enhance face-lock service with improved upload handling and response structure
python / test (push) Failing after 8s
python / test (push) Failing after 8s
- Updated README.md to reflect new features and API changes. - Introduced versioning in app initialization. - Enhanced configuration management in app/config.py with new validation functions. - Refactored main.py to improve request handling and response generation. - Added new models in app/models.py for structured API responses. - Implemented a dedicated UI rendering function in app/ui.py. - Improved Docker configuration for better security and health checks. - Updated tests to cover new validation rules and response formats. - Added CLAUDE.md for project guidelines and working rules.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def render_index_html(*, auth_header_name: str) -> str:
|
||||
return """
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<title>face-lock</title>
|
||||
</head>
|
||||
<body class="bg-slate-950 text-slate-100 min-h-screen">
|
||||
<main class="mx-auto max-w-6xl p-6">
|
||||
<div class="mb-6 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">face-lock</h1>
|
||||
<p class="text-slate-400">Square the subject, crop it, and keep the raw blobs out of sight.</p>
|
||||
</div>
|
||||
<a class="rounded-lg border border-slate-700 px-3 py-2 text-sm text-cyan-300 hover:bg-slate-900" href="/docs" target="_blank" rel="noreferrer">Docs</a>
|
||||
</div>
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-4">
|
||||
<label class="block text-sm text-slate-400">Image</label>
|
||||
<input id="file" type="file" accept="image/*" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3" />
|
||||
<div class="mt-4 grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-sm text-slate-400">Detector</label>
|
||||
<select id="detector" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3">
|
||||
<option value="face">Face</option>
|
||||
<option value="animal">Animal</option>
|
||||
<option value="person">Person</option>
|
||||
<option value="subject" selected>Subject</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-slate-400">Buffer ratio</label>
|
||||
<input id="buffer_ratio" type="number" step="0.05" min="0" max="0.6" value="0.20" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="block text-sm text-slate-400">Auth token (only if enabled)</label>
|
||||
<input id="auth_token" type="password" placeholder="paste token here" class="mt-2 block w-full rounded-lg border border-slate-700 bg-slate-950 p-3" />
|
||||
</div>
|
||||
<button id="go" class="mt-4 rounded-lg bg-cyan-500 px-4 py-2 font-semibold text-slate-950">Process</button>
|
||||
<pre id="meta" class="mt-4 whitespace-pre-wrap rounded-lg bg-slate-950 p-3 text-xs text-slate-300"></pre>
|
||||
</section>
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-4">
|
||||
<div class="mb-3 text-sm font-semibold text-slate-400">Result</div>
|
||||
<div class="grid gap-4">
|
||||
<div>
|
||||
<div class="mb-2 text-xs uppercase tracking-wide text-slate-500">Crop</div>
|
||||
<img id="crop" class="hidden w-full rounded-xl border border-slate-800" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-2 text-xs uppercase tracking-wide text-slate-500">Annotated source</div>
|
||||
<img id="annotated" class="hidden w-full rounded-xl border border-slate-800" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
const file = document.getElementById('file');
|
||||
const go = document.getElementById('go');
|
||||
const crop = document.getElementById('crop');
|
||||
const annotated = document.getElementById('annotated');
|
||||
const meta = document.getElementById('meta');
|
||||
go.onclick = async () => {
|
||||
if (!file.files.length) return;
|
||||
const form = new FormData();
|
||||
form.append('file', file.files[0]);
|
||||
form.append('detector', document.getElementById('detector').value);
|
||||
form.append('buffer_ratio', document.getElementById('buffer_ratio').value);
|
||||
meta.textContent = 'Working...';
|
||||
const headers = {};
|
||||
const token = document.getElementById('auth_token').value.trim();
|
||||
if (token) headers['__AUTH_HEADER_NAME__'] = token;
|
||||
const resp = await fetch('/api/focus', { method: 'POST', body: form, headers });
|
||||
const data = await resp.json();
|
||||
if (!resp.ok) {
|
||||
meta.textContent = JSON.stringify(data, null, 2);
|
||||
return;
|
||||
}
|
||||
meta.textContent = JSON.stringify({
|
||||
filename: data.filename,
|
||||
detector: data.detector,
|
||||
method: data.method,
|
||||
buffer_ratio: data.buffer_ratio,
|
||||
detected_bbox: data.detected_bbox,
|
||||
square_bbox: data.square_bbox,
|
||||
source_size: data.source_size,
|
||||
}, null, 2);
|
||||
crop.src = data.crop_data_url;
|
||||
annotated.src = data.annotated_data_url;
|
||||
crop.classList.remove('hidden');
|
||||
annotated.classList.remove('hidden');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
""".replace("__AUTH_HEADER_NAME__", auth_header_name)
|
||||
Reference in New Issue
Block a user