8a8a89ed3e
- 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.
143 lines
4.4 KiB
Svelte
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>
|