feat: simplify regional end user workbench
This commit is contained in:
@@ -145,6 +145,14 @@ export function DetectionLab({
|
||||
}: DetectionLabProps): JSX.Element {
|
||||
const selectedDetectionModel = detectionModels.find((model) => model.model_id === selectedDetectionModelId) ?? null
|
||||
const selectedModelAsset = modelAssets.find((asset) => asset.model_asset_id === selectedModelAssetId) ?? null
|
||||
const selectedOperatorProfile = DETECTION_OPERATOR_PROFILES.find(
|
||||
(profile) => profile.modelAssetId === selectedModelAssetId,
|
||||
) ?? null
|
||||
const yoloRuntimeReady = Boolean(
|
||||
yoloPreflight?.checks.enabled &&
|
||||
yoloPreflight.checks.dependencies_available &&
|
||||
yoloPreflight.checks.model_file_exists,
|
||||
)
|
||||
const detectionRequiresTileManifest = selectedDetectionModelId === 'yolo-configured'
|
||||
const detectionHasDataset = selectedDetectionDatasetId.length > 0
|
||||
const detectionHasModel = selectedDetectionModel !== null
|
||||
@@ -178,19 +186,19 @@ export function DetectionLab({
|
||||
detectionHasExplicitModelAsset &&
|
||||
detectionHasTileManifest
|
||||
const detectionRunBlockedReason = !selectedProjectId
|
||||
? 'Select or create a project first'
|
||||
? 'De regionale werkruimte is nog niet geladen'
|
||||
: !detectionHasDataset
|
||||
? 'Select a raster dataset'
|
||||
? 'Kies een luchtbeeld'
|
||||
: !detectionHasModel
|
||||
? 'Select a detection model'
|
||||
? 'Kies een analysemodel'
|
||||
: selectedDetectionModelId === 'manual-fixture-detector'
|
||||
? 'Fixture model is explicit test/demo-only'
|
||||
? 'Het fixturemodel is alleen bedoeld voor expliciete tests en demo\'s'
|
||||
: !detectionModelReady
|
||||
? selectedDetectionModel?.limitation_message ?? 'Selected model is not configured'
|
||||
? selectedDetectionModel?.limitation_message ?? 'Het gekozen model is niet geconfigureerd'
|
||||
: !detectionHasExplicitModelAsset
|
||||
? 'Select a local model asset deliberately'
|
||||
? 'Kies een lokaal modelbestand onder beheer'
|
||||
: !detectionHasTileManifest
|
||||
? 'Provide a raster tile manifest for configured YOLO'
|
||||
? 'Maak eerst beeldtegels voor het gekozen luchtbeeld'
|
||||
: null
|
||||
const calibrationRunReady = detectionRunReady && detectionReferenceDatasetId.length > 0 && calibrationThresholdText.trim().length > 0
|
||||
|
||||
@@ -198,18 +206,41 @@ export function DetectionLab({
|
||||
<section className="workspace-panel ai-lab-shell detection-lab-shell">
|
||||
<div className="panel-title-row">
|
||||
<div>
|
||||
<p className="eyebrow">Object detection</p>
|
||||
<h2>Detection Lab</h2>
|
||||
<p className="eyebrow">Analyse van luchtbeelden</p>
|
||||
<h2>Gebouwen herkennen</h2>
|
||||
</div>
|
||||
<button className="secondary-action" type="button" onClick={onLoadModels} disabled={loadingDetectionModels}>
|
||||
Refresh models
|
||||
Status vernieuwen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="ai-user-summary" aria-label="Status gebouwdetectie">
|
||||
<div className="ai-user-summary-card ai-user-summary-card-primary">
|
||||
<span>Actieve analyse</span>
|
||||
<strong>Gebouwdetectie</strong>
|
||||
<p>{selectedOperatorProfile?.displayName ?? selectedModelAsset?.display_name ?? 'Lokaal YOLO-model'}</p>
|
||||
</div>
|
||||
<div className={yoloRuntimeReady ? 'ai-user-summary-card ai-user-summary-card-ready' : 'ai-user-summary-card'}>
|
||||
<span>PyTorch-runtime</span>
|
||||
<strong>{yoloRuntimeReady ? 'Gereed' : loadingDetectionModels ? 'Controleren...' : 'Niet gereed'}</strong>
|
||||
<p>{yoloRuntimeReady ? `${yoloPreflight?.runtime.cuda_available ? 'GPU' : 'CPU'} · lokaal model gevonden` : 'Controleer de modelconfiguratie onder beheer.'}</p>
|
||||
</div>
|
||||
<div className="ai-user-summary-card">
|
||||
<span>Gevalideerde kwaliteit</span>
|
||||
<strong>{selectedOperatorProfile ? `F1 ${selectedOperatorProfile.f1.toFixed(3)}` : 'Nog niet gekoppeld'}</strong>
|
||||
<p>{selectedOperatorProfile ? `${selectedOperatorProfile.positiveSampleCount} testgebieden · resultaten blijven controleplichtig` : 'Kies het goedgekeurde lokale profiel.'}</p>
|
||||
</div>
|
||||
<div className={rasterDatasets.length > 0 ? 'ai-user-summary-card ai-user-summary-card-ready' : 'ai-user-summary-card'}>
|
||||
<span>Beschikbare luchtbeelden</span>
|
||||
<strong>{rasterDatasets.length}</strong>
|
||||
<p>{rasterDatasets.length > 0 ? 'Klaar om een beeld te kiezen.' : 'Laad eerst een gegeorefereerd luchtbeeld in.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details className="ai-lab-model-surface" aria-label="Detection model capabilities">
|
||||
<summary>
|
||||
<span>Model registry</span>
|
||||
<strong>{detectionModels.length} models</strong>
|
||||
<span>Technische modelinformatie</span>
|
||||
<strong>{detectionModels.length} registraties</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-state-stack">
|
||||
@@ -257,21 +288,25 @@ export function DetectionLab({
|
||||
</details>
|
||||
|
||||
{selectedDetectionModelId === 'yolo-configured' ? (
|
||||
<div className="ai-lab-model-surface" aria-label="Local model asset selection">
|
||||
<details className="ai-lab-model-surface" aria-label="Local model asset selection">
|
||||
<summary>
|
||||
<span>Modelkeuze voor beheerders</span>
|
||||
<strong>{selectedOperatorProfile?.displayName ?? selectedModelAsset?.display_name ?? 'Geen lokaal model'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Explicit model asset</h3>
|
||||
<p>Local model assets are read-only. No model file is selected automatically. Choose an existing local model asset before submitting configured YOLO.</p>
|
||||
<h3>Lokaal modelbestand</h3>
|
||||
<p>GeoIntel kiest automatisch het actieve lokale model. Een beheerder kan hier bewust een ander reeds aanwezig, alleen-lezen modelbestand kiezen.</p>
|
||||
</div>
|
||||
<span className={selectedModelAsset ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{selectedModelAsset ? 'asset selected' : 'no explicit asset'}
|
||||
{selectedModelAsset ? 'model gekozen' : 'geen model gekozen'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="model-asset-guidance">
|
||||
<strong>Operator profiles</strong>
|
||||
<strong>Gevalideerde profielen</strong>
|
||||
<p>
|
||||
Candidate profiles apply a local model asset and confidence threshold only after an explicit click.
|
||||
Promoted profiles still require explicit operator action and do not mutate the runtime environment.
|
||||
Een profiel koppelt een lokaal model aan een gemeten zekerheidsdrempel. Een andere keuze geldt alleen voor de huidige analyse en wijzigt de serverconfiguratie niet.
|
||||
</p>
|
||||
</div>
|
||||
<div className="operator-profile-grid" aria-label="Configured YOLO operator profiles">
|
||||
@@ -288,22 +323,22 @@ export function DetectionLab({
|
||||
<div className="operator-profile-card-header">
|
||||
<strong>{profile.displayName}</strong>
|
||||
<span className={profile.defaultApproved ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{profile.defaultApproved ? 'default-approved' : 'Candidate only - not default-approved'}
|
||||
{profile.defaultApproved ? 'standaardprofiel' : 'kandidaat · extra controle vereist'}
|
||||
</span>
|
||||
</div>
|
||||
<p>{profile.description}</p>
|
||||
<div className="operator-profile-metrics">
|
||||
<span>threshold {profile.confidenceThreshold.toFixed(2)}</span>
|
||||
<span>drempel {profile.confidenceThreshold.toFixed(2)}</span>
|
||||
<span>precision {profile.precision.toFixed(3)}</span>
|
||||
<span>recall {profile.recall.toFixed(3)}</span>
|
||||
<span>F1 {profile.f1.toFixed(3)}</span>
|
||||
<span>positive AOIs {profile.positiveSampleCount}</span>
|
||||
<span>max background FP {profile.maxBackgroundDetections}</span>
|
||||
<span>testgebieden {profile.positiveSampleCount}</span>
|
||||
<span>max. achtergrondfouten {profile.maxBackgroundDetections}</span>
|
||||
</div>
|
||||
<div className="entity-meta">
|
||||
<span>asset: {profile.modelAssetId}</span>
|
||||
<span>promotionRecommendation: {profile.promotionRecommendation}</span>
|
||||
<span>available: {profileAsset ? 'yes' : 'not mounted'}</span>
|
||||
<span>modelbestand: {profile.modelAssetId}</span>
|
||||
<span>beoordeling: {profile.promotionRecommendation}</span>
|
||||
<span>beschikbaar: {profileAsset ? 'ja' : 'niet gekoppeld'}</span>
|
||||
</div>
|
||||
<p className="field-guidance">{profile.limitationMessage}</p>
|
||||
<button
|
||||
@@ -312,7 +347,7 @@ export function DetectionLab({
|
||||
onClick={() => onApplyOperatorProfile(profile)}
|
||||
disabled={!profileAsset}
|
||||
>
|
||||
Apply profile
|
||||
Profiel gebruiken
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
@@ -320,17 +355,16 @@ export function DetectionLab({
|
||||
</div>
|
||||
{selectedModelAsset ? (
|
||||
<div className="model-asset-guidance">
|
||||
<strong>Selected model asset status</strong>
|
||||
<strong>Status gekozen model</strong>
|
||||
<p>
|
||||
{selectedModelAsset.display_name} is selected for this browser-run request. Runtime default activation
|
||||
remains a separate guarded operator action backed by a promotion report.
|
||||
{selectedModelAsset.display_name} wordt voor deze analyse gebruikt. De standaard serverconfiguratie blijft ongewijzigd.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<label>
|
||||
Local model file
|
||||
Lokaal modelbestand
|
||||
<select value={selectedModelAssetId} onChange={(event) => onSelectModelAsset(event.target.value)}>
|
||||
<option value="">Select explicit local model asset</option>
|
||||
<option value="">Kies een lokaal modelbestand</option>
|
||||
{modelAssets.map((asset) => (
|
||||
<option key={asset.model_asset_id} value={asset.model_asset_id}>
|
||||
{asset.display_name} {asset.active ? '(active)' : ''}
|
||||
@@ -340,8 +374,8 @@ export function DetectionLab({
|
||||
</label>
|
||||
{modelAssets.length === 0 && !loadingDetectionModels ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No local model assets found.</strong>
|
||||
<p>Mount model files into the backend model directory or continue with the configured YOLO_MODEL_PATH.</p>
|
||||
<strong>Geen lokaal modelbestand gevonden.</strong>
|
||||
<p>Plaats een gecontroleerd model in de modelmap of configureer het bestaande YOLO-modelpad.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedModelAsset ? (
|
||||
@@ -356,13 +390,14 @@ export function DetectionLab({
|
||||
<p>{selectedModelAsset.limitation_message}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
|
||||
<details className="ai-lab-model-surface" aria-label="YOLO runtime preflight">
|
||||
<summary>
|
||||
<span>YOLO runtime preflight</span>
|
||||
<strong>{yoloPreflight?.status ?? 'not loaded'}</strong>
|
||||
<span>Technische runtimecontrole</span>
|
||||
<strong>{yoloRuntimeReady ? 'gereed' : yoloPreflight?.status ?? 'niet geladen'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
@@ -447,72 +482,72 @@ export function DetectionLab({
|
||||
|
||||
<div className="lab-block">
|
||||
<div className="ai-lab-run-surface" aria-label="Detection run controls">
|
||||
<h3>Run detection</h3>
|
||||
<h3>Nieuwe beeldanalyse</h3>
|
||||
<div
|
||||
className={detectionRunReady ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}
|
||||
aria-label="Detection run readiness"
|
||||
>
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Run readiness</h3>
|
||||
<p>Checks the selected dataset, model and tile manifest before submitting a detection job.</p>
|
||||
<h3>Wat is nog nodig?</h3>
|
||||
<p>De analyse start zodra een luchtbeeld en de bijbehorende beeldtegels beschikbaar zijn.</p>
|
||||
</div>
|
||||
<span className={detectionRunReady ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{detectionRunReady ? 'Ready to submit' : 'Blocked'}
|
||||
{detectionRunReady ? 'Klaar om te starten' : 'Nog niet startklaar'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="lab-readiness-grid">
|
||||
<div className={detectionHasDataset ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Raster dataset</span>
|
||||
<strong>{detectionHasDataset ? 'Selected' : 'Select a raster dataset'}</strong>
|
||||
<span>Luchtbeeld</span>
|
||||
<strong>{detectionHasDataset ? 'Geselecteerd' : 'Nog geen luchtbeeld'}</strong>
|
||||
</div>
|
||||
<div className={detectionModelReady ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Model availability</span>
|
||||
<span>Analysemodel</span>
|
||||
<strong>
|
||||
{detectionModelReady
|
||||
? `${selectedDetectionModel?.display_name ?? selectedDetectionModelId} is configured`
|
||||
: selectedDetectionModel?.limitation_message ?? 'Select a configured model'}
|
||||
? 'Lokaal gebouwmodel beschikbaar'
|
||||
: 'Model niet beschikbaar'}
|
||||
</strong>
|
||||
</div>
|
||||
<div className={detectionHasTileManifest ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Tile manifest</span>
|
||||
<span>Beeldtegels</span>
|
||||
<strong>
|
||||
{detectionRequiresTileManifest
|
||||
? detectionHasTileManifest
|
||||
? 'Provided for configured YOLO'
|
||||
: 'Required for configured YOLO'
|
||||
: 'Not required for this model'}
|
||||
? 'Beschikbaar'
|
||||
: 'Maak eerst tegels vanuit het luchtbeeld'
|
||||
: 'Niet vereist'}
|
||||
</strong>
|
||||
</div>
|
||||
<div className={detectionHasExplicitModelAsset ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Local model asset</span>
|
||||
<span>Lokale modelkeuze</span>
|
||||
<strong>
|
||||
{selectedDetectionModelId === 'yolo-configured'
|
||||
? selectedModelAsset
|
||||
? selectedModelAsset.display_name
|
||||
? selectedOperatorProfile?.displayName ?? selectedModelAsset.display_name
|
||||
: modelAssets.length > 0
|
||||
? 'Select a local model asset deliberately'
|
||||
: 'No local assets reported; backend configured path only'
|
||||
: 'Not required for this model'}
|
||||
? 'Kies een lokaal model onder beheer'
|
||||
: 'Geen lokaal model gevonden'
|
||||
: 'Niet vereist'}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={detectionRunReady ? 'lab-action-guardrail lab-action-guardrail-ready' : 'lab-action-guardrail'}>
|
||||
<span>Run action</span>
|
||||
<strong>{detectionRunReady ? 'Ready to submit a detection job' : detectionRunBlockedReason}</strong>
|
||||
<span>Analyse</span>
|
||||
<strong>{detectionRunReady ? 'Klaar om gebouwen te zoeken' : detectionRunBlockedReason}</strong>
|
||||
</div>
|
||||
{rasterDatasets.length === 0 ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No raster datasets available for detection.</strong>
|
||||
<p>Upload or select a raster dataset in Data before running object detection.</p>
|
||||
<strong>Geen luchtbeeld beschikbaar in deze werkruimte.</strong>
|
||||
<p>Voeg onder Bronnen een gegeorefereerde GeoTIFF toe. Daarna kan GeoIntel er beeldtegels en een detectierun van maken.</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="lab-form-grid">
|
||||
<label>
|
||||
Raster dataset
|
||||
Luchtbeeld
|
||||
<select value={selectedDetectionDatasetId} onChange={(event) => onSelectDataset(event.target.value)}>
|
||||
<option value="">Select raster dataset</option>
|
||||
<option value="">Kies een luchtbeeld</option>
|
||||
{rasterDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>
|
||||
{dataset.name}
|
||||
@@ -521,7 +556,7 @@ export function DetectionLab({
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Model
|
||||
Analysemodel
|
||||
<select value={selectedDetectionModelId} onChange={(event) => onSelectModel(event.target.value)}>
|
||||
{detectionModels.map((model) => (
|
||||
<option key={model.model_id} value={model.model_id}>
|
||||
@@ -531,7 +566,7 @@ export function DetectionLab({
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Min confidence
|
||||
Minimale zekerheid
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -542,14 +577,14 @@ export function DetectionLab({
|
||||
/>
|
||||
{selectedDetectionModelId === 'yolo-configured' ? (
|
||||
<span className="field-guidance">
|
||||
Use a validated operator profile, or enter a threshold manually for calibration.
|
||||
Het actieve gevalideerde profiel gebruikt standaard 0,15 voor een evenwichtige gebouwcontrole.
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
</div>
|
||||
{selectedDetectionModelId === 'yolo-configured' ? (
|
||||
<label>
|
||||
Tile manifest
|
||||
Beeldtegelbestand
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Raster tile manifest path"
|
||||
@@ -560,13 +595,13 @@ export function DetectionLab({
|
||||
) : null}
|
||||
{selectedDetectionModelId === 'yolo-configured' && detectionTileManifestPath.trim() ? (
|
||||
<div className="linked-manifest-card">
|
||||
<strong>Linked tile manifest</strong>
|
||||
<strong>Gekoppelde beeldtegels</strong>
|
||||
<p>{detectionTileManifestPath}</p>
|
||||
<span>Refresh preflight after changing model asset or manifest path.</span>
|
||||
<span>De technische controle wordt vernieuwd wanneer het model of tegelbestand wijzigt.</span>
|
||||
</div>
|
||||
) : null}
|
||||
<button className="primary-action" type="button" onClick={onRunDetection} disabled={runningDetection || !detectionRunReady}>
|
||||
Run detection
|
||||
Zoek gebouwen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -590,7 +625,12 @@ export function DetectionLab({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="ai-lab-run-surface guided-calibration-surface" aria-label="Guided calibration runner">
|
||||
<details className="ai-lab-model-surface guided-calibration-surface" aria-label="Guided calibration runner">
|
||||
<summary>
|
||||
<span>Modelkalibratie voor beheerders</span>
|
||||
<strong>{detectionCalibrationRows.length > 0 ? `${detectionCalibrationRows.length} drempels getest` : 'gesloten'}</strong>
|
||||
</summary>
|
||||
<div className="ai-lab-disclosure-body">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Guided calibration runner</h3>
|
||||
@@ -705,23 +745,24 @@ export function DetectionLab({
|
||||
<p>Choose a reference dataset and threshold set, then start the explicit sweep.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div className="ai-lab-results-surface" aria-label="Detection results">
|
||||
<div className="panel-title-row">
|
||||
<div>
|
||||
<h3>Detection results</h3>
|
||||
<p className="muted">Load persisted detections and filter by class or confidence.</p>
|
||||
<h3>Gevonden objecten</h3>
|
||||
<p className="muted">Bekijk eerder bewaarde analyses en filter op type of zekerheid.</p>
|
||||
</div>
|
||||
<button className="secondary-action" type="button" onClick={onLoadRuns} disabled={!selectedProjectId}>
|
||||
Refresh runs
|
||||
Analyses vernieuwen
|
||||
</button>
|
||||
</div>
|
||||
<div className="lab-form-grid">
|
||||
<label>
|
||||
Run
|
||||
Analyse
|
||||
<select value={selectedDetectionRunId} onChange={(event) => onSelectRun(event.target.value)}>
|
||||
<option value="">Select detection run</option>
|
||||
<option value="">Kies een bewaarde analyse</option>
|
||||
{detectionRuns.map((run) => (
|
||||
<option key={run.id} value={run.id}>
|
||||
{run.model_name || 'detection'} - {run.status} - {run.id}
|
||||
@@ -730,16 +771,16 @@ export function DetectionLab({
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Class
|
||||
Type object
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Class filter"
|
||||
placeholder="bijvoorbeeld building"
|
||||
value={detectionClassFilter}
|
||||
onChange={(event) => onSetClassFilter(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Min confidence
|
||||
Minimale zekerheid
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -751,18 +792,18 @@ export function DetectionLab({
|
||||
</label>
|
||||
</div>
|
||||
<button className="primary-action" type="button" onClick={onLoadResults} disabled={!selectedDetectionRunId || loadingDetectionResults}>
|
||||
Load detections
|
||||
Resultaten laden
|
||||
</button>
|
||||
{loadingDetectionResults ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>Loading detection results.</strong>
|
||||
<p>Retrieving persisted detections for the selected run.</p>
|
||||
<strong>Resultaten laden.</strong>
|
||||
<p>De bewaarde objecten van deze analyse worden opgehaald.</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="ai-lab-state-stack">
|
||||
<div className="result-state result-state-ready">
|
||||
<strong>Detections loaded: {detectionItems.length}</strong>
|
||||
<p>{selectedDetectionRunId ? 'Loaded from persisted detection records.' : 'Select a detection run before loading results.'}</p>
|
||||
<strong>{detectionItems.length} objecten geladen</strong>
|
||||
<p>{selectedDetectionRunId ? 'Deze resultaten zijn bewaard in de database.' : 'Kies eerst een bewaarde analyse.'}</p>
|
||||
</div>
|
||||
</div>
|
||||
{detectionItems.length > 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user