Jonas c5fd4de09b Implement labeling frontend and the backend support it needs
Add a framework-free HTML/CSS/JS labeling UI: set/session/status
filters, a frame browser, drag-to-draw/move/resize bounding boxes with
a per-set class picker, per-frame status control, and Left/Right frame
navigation. No login - a locally cached username is sent for
attribution only, matching the backend's get-or-create user model.

Backend additions the frontend needed: serve frame images from a
configurable FRAMES_ROOT via a /images static mount, permissive CORS
(internal tool, not publicly exposed), and a GET /sets/{id}/frames
endpoint returning frames joined with their per-set label status
(defaulting missing rows to unlabeled) for the frame browser.

Verified the full call chain end-to-end against a running backend +
static frontend server (set/class creation, frame ingest, image
serving, label CRUD, status updates, CORS preflight) - every field
name the JS reads matches the API responses. 18/18 backend tests
pass. Not yet verified: actual interactive browser use (no browser
tooling available here) - try drag-to-draw/resize locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 11:34:26 +02:00

75 lines
1.9 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SpelunkAI Labeling Tool</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="topbar">
<h1>SpelunkAI Labeling</h1>
<label>
Set
<select id="set-select"><option value="">Loading…</option></select>
</label>
<label>
Session
<input id="session-filter" type="text" placeholder="all sessions" />
</label>
<label>
Status
<select id="status-filter">
<option value="">all</option>
<option value="unlabeled">unlabeled</option>
<option value="auto_labeled">auto_labeled</option>
<option value="reviewed">reviewed</option>
</select>
</label>
<button id="apply-filters">Apply</button>
<span id="frame-position" class="frame-position"></span>
</header>
<main class="workspace">
<section class="canvas-area">
<div id="image-container">
<img id="frame-image" draggable="false" alt="" />
<div id="bbox-layer"></div>
</div>
<div class="nav-controls">
<button id="prev-frame">&larr; Prev</button>
<label>
Frame status
<select id="frame-status" disabled>
<option value="unlabeled">unlabeled</option>
<option value="auto_labeled">auto_labeled</option>
<option value="reviewed">reviewed</option>
</select>
</label>
<button id="next-frame">Next &rarr;</button>
</div>
</section>
<aside class="sidebar">
<h2>Classes</h2>
<div id="class-list"><p class="hint">Pick a set first.</p></div>
<p class="hint">
Click a class, then drag on the image to draw a box.<br />
Click an existing box to select it (Delete/Backspace removes it, drag
the body to move, drag the bottom-right handle to resize).<br />
Left/Right arrow keys move between frames.
</p>
</aside>
</main>
<script src="app.js" defer></script>
</body>
</html>