File size: 5,250 Bytes
cbff690 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>UrbanFix AI — local test upload</title>
<style>
:root {
font-family: system-ui, Segoe UI, Roboto, sans-serif;
--bg: #0f1419;
--card: #1a2332;
--text: #e7edf4;
--muted: #8b9cb3;
--accent: #3b82f6;
--ok: #22c55e;
}
* { box-sizing: border-box; }
body {
margin: 0; min-height: 100vh; background: var(--bg); color: var(--text);
padding: 1.5rem; line-height: 1.5;
}
h1 { font-size: 1.25rem; font-weight: 600; margin: 0 0 0.5rem; }
p.muted { color: var(--muted); font-size: 0.9rem; margin: 0 0 1rem; }
.card {
background: var(--card); border-radius: 12px; padding: 1.25rem;
max-width: 720px; margin: 0 auto;
border: 1px solid rgba(255,255,255,.06);
}
label { display: block; font-size: 0.85rem; color: var(--muted); margin-bottom: 0.35rem; }
input[type="text"], input[type="file"] {
width: 100%; padding: 0.5rem 0.65rem; border-radius: 8px;
border: 1px solid rgba(255,255,255,.12); background: #0d1117; color: var(--text);
margin-bottom: 0.9rem;
}
.row { display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; margin-bottom: 0.9rem; }
.row label { margin: 0; display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
button {
background: var(--accent); color: #fff; border: none; padding: 0.6rem 1.2rem;
border-radius: 8px; font-weight: 600; cursor: pointer;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
button:hover:not(:disabled) { filter: brightness(1.08); }
pre {
background: #0d1117; padding: 1rem; border-radius: 8px; overflow: auto;
font-size: 0.8rem; border: 1px solid rgba(255,255,255,.08); max-height: 70vh;
}
.preview { max-width: 100%; max-height: 220px; border-radius: 8px; margin-top: 0.5rem; }
.err { color: #f87171; font-size: 0.9rem; margin-top: 0.5rem; }
.meta { font-size: 0.8rem; color: var(--ok); margin-top: 0.5rem; }
</style>
</head>
<body>
<div class="card">
<h1>UrbanFix AI — image test</h1>
<p class="muted">
Upload a civic photo. First Space boot downloads models — wait until <a href="/health">/health</a> says <code>ok</code>.
</p>
<label for="baseUrl">API base URL</label>
<input id="baseUrl" type="text" value="" autocomplete="off" />
<label for="apiKey">API key (must match <code>AI_API_KEY</code> in <code>.env</code>)</label>
<input id="apiKey" type="text" placeholder="Bearer token value" autocomplete="off" />
<div class="row">
<label><input type="checkbox" id="skipValidation" /> Skip validation (Stage 0)</label>
</div>
<label for="file">Image</label>
<input id="file" type="file" accept="image/*" />
<img id="preview" class="preview" alt="" hidden />
<p style="margin-top: 1rem;">
<button type="button" id="runBtn">Run /analyze</button>
</p>
<div id="err" class="err"></div>
<div id="meta" class="meta"></div>
<pre id="out">{}</pre>
</div>
<script>
const $ = (id) => document.getElementById(id);
if (!$("baseUrl").value) $("baseUrl").value = window.location.origin;
const baseUrl = () => ($("baseUrl").value || window.location.origin).replace(/\/$/, "");
const apiKey = () => $("apiKey").value.trim();
$("file").addEventListener("change", () => {
const f = $("file").files[0];
const img = $("preview");
if (!f) { img.hidden = true; return; }
img.src = URL.createObjectURL(f);
img.hidden = false;
});
$("runBtn").addEventListener("click", async () => {
const file = $("file").files[0];
$("err").textContent = "";
$("meta").textContent = "";
$("out").textContent = "…loading";
if (!file) {
$("err").textContent = "Choose an image first.";
$("out").textContent = "{}";
return;
}
const fd = new FormData();
fd.append("file", file);
let url = new URL(baseUrl() + "/analyze");
if ($("skipValidation").checked) url.searchParams.set("skip_validation", "true");
const headers = {};
const key = apiKey();
if (key) headers["Authorization"] = "Bearer " + key;
$("runBtn").disabled = true;
const t0 = performance.now();
try {
const res = await fetch(url.toString(), { method: "POST", headers, body: fd });
const text = await res.text();
let data;
try { data = JSON.parse(text); } catch { data = { raw: text }; }
if (!res.ok) {
$("err").textContent = res.status + " " + res.statusText + (data.detail ? ": " + JSON.stringify(data.detail) : "");
$("out").textContent = JSON.stringify(data, null, 2);
return;
}
$("meta").textContent = "OK in " + Math.round(performance.now() - t0) + " ms";
$("out").textContent = JSON.stringify(data, null, 2);
} catch (e) {
$("err").textContent = String(e.message || e);
$("out").textContent = "{}";
} finally {
$("runBtn").disabled = false;
}
});
</script>
</body>
</html>
|