Files
SlabUploader/frontend/src/App.svelte
T
Ty 8a8a89ed3e feat(inference): S7 Call 1/Call 2 + prompt revision REST + test-inference + Generate text UI
- backend/inference.py: prompt seed (rev 0) + append-only revision REST
  (POST /prompts/{call}/revisions, /activate, GET; METADATA-105/106). Call 1 vision
  classification (1024px processed photos + synced taxonomy, 0.7 confidence gate,
  METADATA-35/40/45), Call 2 long description (user-triggered, METADATA-55/60/65),
  inference_log per call (METADATA-66), inference-off server_description (METADATA-70),
  test-inference probe (SETTINGS-45). Stdlib urllib, no new deps.
- frontend: create/inference/GenerateText.svelte (control visible only when content
  enabled + endpoint reachable, METADATA-110; Call 2 + Call 1 buttons) + dev toggle.
- tests: 6 inference tests (seed+revision, call1 gate, call2 stores desc, off->409,
  server desc, test-inference unconfigured).

Verified: pytest 54 passed; gates exit 0; npm build ok.
2026-09-19 17:04:01 -07:00

143 lines
4.4 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { supported } from './browser';
import { createCaptureSession, type CaptureSession } from './capture/session';
import type { Capture } from './capture/capture.types';
import Resume from './capture/Resume.svelte';
import BatchPhotos from './capture/BatchPhotos.svelte';
import PickCalibration from './capture/PickCalibration.svelte';
import MaskPanel from './capture/MaskPanel.svelte';
import AxisPanel from './capture/AxisPanel.svelte';
import MeasurePanel from './capture/MeasurePanel.svelte';
import Settings from './create/settings/Settings.svelte';
import SlabList from './create/slabs/SlabList.svelte';
import PhotoAttach from './create/photos/PhotoAttach.svelte';
import Review from './create/review/Review.svelte';
import Publish from './create/publish/Publish.svelte';
import GenerateText from './create/inference/GenerateText.svelte';
const gate = typeof window !== 'undefined' ? supported() : 'window unavailable';
const session: CaptureSession = createCaptureSession();
let cap: Capture = { k: 'batch', photos: [] };
let busy = false;
let pngUrl: string | null = null;
let showSettings = false;
let showSlabs = false;
let showPhotos = false;
let showReview = false;
let showPublish = false;
let showGenerate = false;
const unsubCap = session.capture.subscribe((c) => {
cap = c;
pngUrl = c.k === 'measure' ? session.getInventoryUrl() : null;
});
const unsubBusy = session.busy.subscribe((b) => {
busy = b;
});
onMount(() => {
void session.resumeProbe();
});
onDestroy(() => {
unsubCap();
unsubBusy();
session.dispose();
});
</script>
{#if gate}
<main class="hardstop">
<h1>Unsupported browser</h1>
<p>
SlabUploader needs a modern browser with canvas, workers, and IndexedDB support:
iOS Safari, Android Chrome, or desktop Chrome, Edge, or Firefox.
</p>
<p class="detail">Missing: {gate}</p>
</main>
{:else}
<main class="app">
{#if cap.k === 'resume'}
<Resume onContinue={() => session.continueDraft()} onFresh={() => void session.startFresh()} />
{:else if cap.k === 'batch'}
<BatchPhotos
photos={cap.photos}
onAdd={(f) => void session.addToBatch(f)}
onRemove={session.removeFromBatch}
onContinue={session.continueBatch}
/>
{:else if cap.k === 'pickCalibration'}
<PickCalibration
photos={cap.photos}
onPick={session.pickCalibration}
onBack={session.back}
/>
{:else if cap.k === 'mask'}
<MaskPanel
state={cap}
{busy}
setSensitivity={session.setSensitivity}
setSheet={session.setSheet}
approveMask={session.approveMask}
retakes={session.retakes}
back={session.back}
/>
{:else if cap.k === 'axis'}
<AxisPanel
state={cap}
setAxisAngle={session.setAxisAngle}
approveAxis={session.approveAxis}
back={session.back}
/>
{:else}
<MeasurePanel
state={cap}
setLength={session.setLength}
setThickness={session.setThickness}
calculate={session.calculate}
back={session.back}
{pngUrl}
/>
{/if}
<div style="margin-top:1rem;display:flex;gap:8px">
<button on:click={() => (showSettings = !showSettings)}>Settings (dev)</button>
<button on:click={() => (showSlabs = !showSlabs)}>Slabs (dev)</button>
<button on:click={() => (showPhotos = !showPhotos)}>Photos (dev)</button>
<button on:click={() => (showReview = !showReview)}>Review (dev)</button>
<button on:click={() => (showPublish = !showPublish)}>Publish (dev)</button>
<button on:click={() => (showGenerate = !showGenerate)}>Generate (dev)</button>
</div>
{#if showSettings}
<Settings />
{/if}
{#if showSlabs}
<SlabList />
{/if}
{#if showPhotos}
<PhotoAttach />
{/if}
{#if showReview}
<Review />
{/if}
{#if showPublish}
<Publish />
{/if}
{#if showGenerate}
<GenerateText />
{/if}
</main>
{/if}
<style>
:global(body) {
margin: 0;
background: #121212;
font-family: system-ui, -apple-system, Segoe UI, sans-serif;
}
.app, .hardstop { max-width: 720px; margin: 0 auto; min-height: 100vh; }
.hardstop { padding: 48px 24px; color: #eee; text-align: center; }
.hardstop h1 { font-size: 1.6rem; }
.detail { color: #888; font-size: 0.85rem; }
</style>