feat: simplify regional workbench language
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 23:15:50 +02:00
parent 0381fdd636
commit b50b250f72
18 changed files with 292 additions and 97 deletions
@@ -1,4 +1,5 @@
import type { AreaRead, DatasetCreateResponse, ExportRead, ProjectRead, QualityCheckRead } from '../types'
import { REGIONAL_WORKSPACE_LABEL, REGIONAL_WORKSPACE_PROJECT_NAME } from '../config/primaryFocus'
interface StatusItem {
key: string
@@ -26,6 +27,17 @@ function countReferenceDatasets(datasets: DatasetCreateResponse[]): number {
return datasets.filter((dataset) => dataset.dataset_role === 'reference').length
}
function statusLabel(state: StatusItem['state']): string {
if (state === 'ready') return 'gereed'
if (state === 'warning') return 'aandacht'
return 'nog open'
}
function projectLabel(project: ProjectRead | null): string {
if (!project) return 'Geen werkruimte'
return project.name === REGIONAL_WORKSPACE_PROJECT_NAME ? REGIONAL_WORKSPACE_LABEL : project.name
}
function nextAction(items: StatusItem[]): string {
if (!items.some((item) => item.key === 'project' && item.state === 'ready')) {
return 'De regionale werkruimte kon niet worden geopend.'
@@ -65,7 +77,7 @@ export function WorkbenchStatusStrip({
{
key: 'project',
label: 'Werkruimte',
value: selectedProject ? selectedProject.name : 'Geen werkruimte',
value: projectLabel(selectedProject),
detail: selectedProject ? selectedProject.region : 'Niet geladen',
state: selectedProject ? 'ready' : 'waiting',
},
@@ -114,12 +126,12 @@ export function WorkbenchStatusStrip({
<div>
<p className="eyebrow">Platformstatus</p>
<h2>
<span className="sr-only">Workbench status: </span>
<span className="sr-only">Status werkruimte: </span>
{coreReady ? 'Kaartwerkruimte is gebruiksklaar' : 'Kaartwerkruimte vraagt aandacht'}
</h2>
<p className="status-next-action">{nextAction(items)}</p>
</div>
<div className="status-readiness" aria-label={`${readyCount} of ${items.length} workbench stages ready`}>
<div className="status-readiness" aria-label={`${readyCount} van ${items.length} onderdelen gereed`}>
<strong>{readyCount}/{items.length}</strong>
<span>onderdelen gereed</span>
</div>
@@ -129,7 +141,7 @@ export function WorkbenchStatusStrip({
<div className={`status-tile status-tile-${item.state}`} key={item.key}>
<div className="status-tile-topline">
<span>{item.label}</span>
<span className="status-pill">{item.state}</span>
<span className="status-pill">{statusLabel(item.state)}</span>
</div>
<strong title={item.value}>{item.value}</strong>
<p>{item.detail}</p>
@@ -1,6 +1,6 @@
import type { Dispatch, FormEvent, SetStateAction } from 'react'
import type { AreaRead, DatasetCreateResponse } from '../../types'
import { getDatasetDisplayName } from '../../lib/datasetDisplay'
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
interface DatasetFormState {
datasetType: string
@@ -63,6 +63,9 @@ function normalizeDatasetRole(dataset: DatasetCreateResponse): 'reference' | 'ca
if (dataset.dataset_role === 'reference') {
return 'reference'
}
if (dataset.dataset_role === 'source') {
return 'source'
}
if (dataset.dataset_role === 'derived' || isVectorDatasetType(dataset.dataset_type)) {
return 'candidate'
}
@@ -76,7 +79,7 @@ function datasetRoleLabel(role: string): string {
if (role === 'candidate') {
return 'Analyse-resultaat'
}
return 'Eigen bron'
return 'Basisbron'
}
function datasetActionHint(dataset: DatasetCreateResponse, role: string): string {
@@ -92,6 +95,19 @@ function datasetActionHint(dataset: DatasetCreateResponse, role: string): string
return 'Ingeladen gegevensbron voor verdere analyse.'
}
function formatObservationDate(value: string | null | undefined): string {
if (!value) return 'huidige toestand'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return value
return new Intl.DateTimeFormat('nl-BE', { day: 'numeric', month: 'short', year: 'numeric' }).format(date)
}
function datasetKindLabel(dataset: DatasetCreateResponse): string {
if (dataset.dataset_type === 'raster') return 'Luchtbeeld'
if (isVectorDatasetType(dataset.dataset_type)) return 'Kaartlaag'
return 'Gegevensbron'
}
export function DatasetPanel({
selectedProjectId,
selectedDatasetId,
@@ -107,8 +123,24 @@ export function DatasetPanel({
onOpenDatasetInMap,
onOpenDatasetExport,
}: DatasetPanelProps) {
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
const selectedDataset = datasets.find((dataset) => dataset.id === selectedDatasetId)
const latestDatasetBySeries = new Map<string, DatasetCreateResponse>()
datasets.forEach((dataset) => {
if (!dataset.temporal_series_key) return
const current = latestDatasetBySeries.get(dataset.temporal_series_key)
const currentTimestamp = current?.observed_at ? Date.parse(current.observed_at) : Number.NEGATIVE_INFINITY
const candidateTimestamp = dataset.observed_at ? Date.parse(dataset.observed_at) : Number.NEGATIVE_INFINITY
if (!current || candidateTimestamp > currentTimestamp) {
latestDatasetBySeries.set(dataset.temporal_series_key, dataset)
}
})
const primaryDatasets = datasets.filter(
(dataset) => !dataset.temporal_series_key || latestDatasetBySeries.get(dataset.temporal_series_key)?.id === dataset.id,
)
const historicalDatasets = datasets.filter(
(dataset) => dataset.temporal_series_key && latestDatasetBySeries.get(dataset.temporal_series_key)?.id !== dataset.id,
)
const readyDatasets = primaryDatasets.filter((dataset) => dataset.status === 'ready').length
const roleSummaries = [
{
key: 'selected',
@@ -120,21 +152,21 @@ export function DatasetPanel({
{
key: 'reference',
label: 'Referentie',
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'reference').length,
count: primaryDatasets.filter((dataset) => normalizeDatasetRole(dataset) === 'reference').length,
hint: 'Officieel',
className: 'dataset-role-reference',
},
{
key: 'candidate',
label: 'Resultaat',
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'candidate').length,
count: primaryDatasets.filter((dataset) => normalizeDatasetRole(dataset) === 'candidate').length,
hint: 'Afgeleid',
className: 'dataset-role-candidate',
},
{
key: 'source',
label: 'Eigen bron',
count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'source').length,
label: 'Basisbron',
count: primaryDatasets.filter((dataset) => normalizeDatasetRole(dataset) === 'source').length,
hint: 'Ingeladen',
className: 'dataset-role-source',
},
@@ -147,7 +179,7 @@ export function DatasetPanel({
<p className="eyebrow">Ingeladen gegevens</p>
<h2>Bronnen</h2>
</div>
<span className="count-pill">{readyDatasets}/{datasets.length} beschikbaar</span>
<span className="count-pill">{readyDatasets}/{primaryDatasets.length} bronnen beschikbaar</span>
</div>
<div className="data-selection-summary data-selection-summary-dataset">
@@ -165,17 +197,17 @@ export function DatasetPanel({
value={datasetForm.datasetType}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, datasetType: event.target.value }))}
>
<option value="vector">vector</option>
<option value="geojson">geojson</option>
<option value="raster">raster</option>
<option value="vector">Kaartlaag</option>
<option value="geojson">GeoJSON-kaartlaag</option>
<option value="raster">Luchtbeeld of raster</option>
</select>
</label>
<label>
Bronnaam
Naam van de bron
<input
value={datasetForm.source}
onChange={(event) => onDatasetFormChange((previous) => ({ ...previous, source: event.target.value }))}
placeholder="user_upload"
placeholder="bijvoorbeeld eigen luchtbeeld"
/>
</label>
<label>
@@ -226,7 +258,7 @@ export function DatasetPanel({
<div className="data-panel-list-block dataset-catalog-block">
<p className="data-section-label">Beschikbare bronnen</p>
<ul className="dataset-list">
{datasets.map((dataset) => {
{primaryDatasets.map((dataset) => {
const datasetRole = normalizeDatasetRole(dataset)
const roleLabel = datasetRoleLabel(datasetRole)
return (
@@ -235,12 +267,12 @@ export function DatasetPanel({
<div>
<div className="dataset-card-kicker">
<span className={`dataset-role-badge dataset-role-${datasetRole}`}>{roleLabel}</span>
<span>{dataset.dataset_type}</span>
<span>{datasetKindLabel(dataset)}</span>
</div>
<strong className="dataset-card-title">{getDatasetDisplayName(dataset)}</strong>
<div className="entity-meta dataset-card-primary-meta">
<span>{(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten</span>
<span>{dataset.source_name ?? dataset.source}</span>
<span>{getDatasetSourceDisplayName(dataset)} · {formatObservationDate(dataset.observed_at)}</span>
</div>
</div>
<span className={dataset.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
@@ -305,6 +337,28 @@ export function DatasetPanel({
)
})}
</ul>
{historicalDatasets.length > 0 ? (
<details className="dataset-history-disclosure">
<summary>
<span>Historische meetmomenten</span>
<strong>{historicalDatasets.length} oudere lagen</strong>
</summary>
<ul className="dataset-history-list">
{historicalDatasets.map((dataset) => (
<li key={dataset.id}>
<div>
<strong>{getDatasetDisplayName(dataset)}</strong>
<span>{formatObservationDate(dataset.observed_at)} · {(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten</span>
</div>
<div className="panel-action-row">
<button className="secondary-action" type="button" onClick={() => onOpenDatasetInMap(dataset)}>Kaart</button>
<button className="secondary-action" type="button" onClick={() => onOpenDatasetExport(dataset)}>Downloaden</button>
</div>
</li>
))}
</ul>
</details>
) : null}
</div>
</section>
)
@@ -16,6 +16,13 @@ import { DETECTION_OPERATOR_PROFILES, type DetectionOperatorProfile } from './de
const DETECTION_PAGE_SIZE_OPTIONS = [25, 50, 100] as const
const DEFAULT_DETECTION_PAGE_SIZE = 50
function detectionModelLabel(model: DetectionModelCapability): string {
if (model.model_id === 'yolo-configured') return 'Lokaal gebouwmodel'
if (model.model_id === 'manual-fixture-detector') return 'Testmodel (alleen voor demo)'
if (model.model_id === 'yolo-placeholder') return 'Gebouwmodel nog niet geconfigureerd'
return model.display_name
}
interface CalibrationRow {
analysisRunId: string
qualityCheckId: string
@@ -272,7 +279,7 @@ export function DetectionLab({
<ul className="model-list">
{detectionModels.map((model) => (
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
<strong>{model.display_name}</strong>
<strong>{detectionModelLabel(model)}</strong>
<span className={model.configured ? 'status-badge status-badge-ready' : 'status-badge'}>{model.status}</span>
<div className="entity-meta">
<span>{model.model_id}</span>
@@ -560,7 +567,7 @@ export function DetectionLab({
<select value={selectedDetectionModelId} onChange={(event) => onSelectModel(event.target.value)}>
{detectionModels.map((model) => (
<option key={model.model_id} value={model.model_id}>
{model.display_name}
{detectionModelLabel(model)}
</option>
))}
</select>
@@ -587,7 +594,7 @@ export function DetectionLab({
Beeldtegelbestand
<input
type="text"
placeholder="Raster tile manifest path"
placeholder="Pad naar de aangemaakte beeldtegels"
value={detectionTileManifestPath}
onChange={(event) => onSetTileManifestPath(event.target.value)}
/>
@@ -609,17 +616,17 @@ export function DetectionLab({
<div className="ai-lab-state-stack">
{detectionRunError ? (
<div className="result-state result-state-error">
<strong>Detection run failed.</strong>
<strong>De beeldanalyse is mislukt.</strong>
<p>{detectionRunError}</p>
</div>
) : null}
{detectionRunResult ? (
<div className="result-summary-card">
<p>Status: {detectionRunResult.status}</p>
<p>Message: {detectionRunResult.message}</p>
<p>Analysis run: {detectionRunResult.analysis_run_id}</p>
<p>Job: {detectionRunResult.job_id}</p>
<p>Detections: {detectionRunResult.detection_count}</p>
<p>Uitleg: {detectionRunResult.message}</p>
<p>Analyse: {detectionRunResult.analysis_run_id}</p>
<p>Verwerking: {detectionRunResult.job_id}</p>
<p>Gevonden objecten: {detectionRunResult.detection_count}</p>
{detectionRunResult.error_code ? <p className="error">Code: {detectionRunResult.error_code}</p> : null}
</div>
) : null}
@@ -765,7 +772,7 @@ export function DetectionLab({
<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}
{run.model_name || 'Gebouwdetectie'} · {run.status} · {run.id}
</option>
))}
</select>
@@ -774,7 +781,7 @@ export function DetectionLab({
Type object
<input
type="text"
placeholder="bijvoorbeeld building"
placeholder="bijvoorbeeld gebouw"
value={detectionClassFilter}
onChange={(event) => onSetClassFilter(event.target.value)}
/>
@@ -811,10 +818,10 @@ export function DetectionLab({
<div className="pagination-toolbar" aria-label="Detection result pagination">
<p className="pagination-summary" aria-live="polite">
<strong>{detectionPageStart + 1}-{detectionPageEnd}</strong>
<span>of {detectionItems.length}</span>
<span>van {detectionItems.length}</span>
</p>
<label className="pagination-page-size">
Rows
Rijen
<select
value={detectionPageSize}
onChange={(event) => {
@@ -831,19 +838,19 @@ export function DetectionLab({
<button
className="secondary-action pagination-button"
type="button"
aria-label="Previous detection results page"
title="Previous page"
aria-label="Vorige resultatenpagina"
title="Vorige pagina"
disabled={currentDetectionPage <= 1}
onClick={() => setDetectionResultPage(currentDetectionPage - 1)}
>
{'<'}
</button>
<span>Page {currentDetectionPage} of {detectionPageCount}</span>
<span>Pagina {currentDetectionPage} van {detectionPageCount}</span>
<button
className="secondary-action pagination-button"
type="button"
aria-label="Next detection results page"
title="Next page"
aria-label="Volgende resultatenpagina"
title="Volgende pagina"
disabled={currentDetectionPage >= detectionPageCount}
onClick={() => setDetectionResultPage(currentDetectionPage + 1)}
>
@@ -855,10 +862,10 @@ export function DetectionLab({
<table>
<thead>
<tr>
<th>Class</th>
<th>Confidence</th>
<th>Type</th>
<th>Zekerheid</th>
<th>Model</th>
<th>Source tile</th>
<th>Beeldtegel</th>
</tr>
</thead>
<tbody>
@@ -879,34 +886,40 @@ export function DetectionLab({
) : null}
</div>
<div className="ai-lab-results-surface calibration-comparison-surface" aria-label="Detection calibration comparison">
<details className="secondary-analysis-disclosure detection-technical-evaluation">
<summary>
<span>Technische modelevaluatie</span>
<strong>{calibrationRows.length > 0 || detectionQaResult ? 'resultaten beschikbaar' : 'optioneel'}</strong>
</summary>
<div className="ai-lab-disclosure-body">
<div className="ai-lab-results-surface calibration-comparison-surface" aria-label="Vergelijking modelkalibratie">
<div className="panel-title-row">
<div>
<h3>Calibration comparison</h3>
<p className="muted">Compare persisted detection runs by confidence threshold before promoting a model setting.</p>
<h3>Kalibraties vergelijken</h3>
<p className="muted">Vergelijk bewaarde analyseruns per zekerheidsdrempel voordat een modelinstelling wordt goedgekeurd.</p>
</div>
<span className="count-pill">{calibrationRows.length} rows</span>
<span className="count-pill">{calibrationRows.length} resultaten</span>
</div>
{calibrationRows.length > 0 ? (
<>
<div className="calibration-summary-grid" aria-label="Calibration comparison winners">
<CalibrationSummaryCard title="Best F1 candidate" row={bestF1Candidate} metric="f1" />
<CalibrationSummaryCard title="Best precision candidate" row={bestPrecisionCandidate} metric="precision" />
<CalibrationSummaryCard title="Lowest false-positive pressure" row={lowestFalsePositivePressureCandidate} metric="falsePositives" />
<CalibrationSummaryCard title="Beste F1-score" row={bestF1Candidate} metric="f1" />
<CalibrationSummaryCard title="Beste precisie" row={bestPrecisionCandidate} metric="precision" />
<CalibrationSummaryCard title="Minste foutieve meldingen" row={lowestFalsePositivePressureCandidate} metric="falsePositives" />
</div>
<div className="table-scroll">
<table>
<thead>
<tr>
<th>Threshold</th>
<th>Drempel</th>
<th>Model</th>
<th>Detections</th>
<th>Precision</th>
<th>Objecten</th>
<th>Precisie</th>
<th>Recall</th>
<th>F1</th>
<th>False positives</th>
<th>False negatives</th>
<th>Quality check</th>
<th>Fout positief</th>
<th>Fout negatief</th>
<th>Kwaliteitscontrole</th>
</tr>
</thead>
<tbody>
@@ -915,7 +928,7 @@ export function DetectionLab({
<td>{formatNullableNumber(row.threshold, 2)}</td>
<td>
<strong>{row.modelName}</strong>
<span className="table-subtle">{row.modelAssetId ?? 'runtime configured path'}</span>
<span className="table-subtle">{row.modelAssetId ?? 'geconfigureerd lokaal model'}</span>
</td>
<td>{row.detectionCount ?? 'n/a'}</td>
<td>{formatNullableNumber(row.precision, 3)}</td>
@@ -930,24 +943,24 @@ export function DetectionLab({
</table>
</div>
<div className="lab-action-guardrail">
<span>Promotion guardrail</span>
<strong>Promote only after checking evidence across AOIs, false positives and false negatives.</strong>
<span>Voorwaarde voor goedkeuring</span>
<strong>Keur pas goed nadat meerdere gebieden en de foutieve positieve en negatieve resultaten zijn gecontroleerd.</strong>
</div>
</>
) : (
<div className="result-state result-state-empty">
<strong>No calibration comparison available yet.</strong>
<p>Run configured YOLO at multiple confidence thresholds, then compare each persisted detection run against the same reference dataset.</p>
<strong>Nog geen kalibratievergelijking beschikbaar.</strong>
<p>Voer het lokale model met meerdere zekerheidsdrempels uit en vergelijk de resultaten met dezelfde referentielaag.</p>
</div>
)}
</div>
<div className="ai-lab-qa-surface" aria-label="Detection QA controls and results">
<h3>Detection QA</h3>
<div className="ai-lab-qa-surface" aria-label="Kwaliteitscontrole gebouwdetectie">
<h3>Kwaliteitscontrole gebouwdetectie</h3>
<label>
Reference dataset
Referentielaag
<select value={detectionReferenceDatasetId} onChange={(event) => onSelectReferenceDataset(event.target.value)}>
<option value="">Select reference dataset</option>
<option value="">Kies een referentielaag</option>
{referenceDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
@@ -956,24 +969,24 @@ export function DetectionLab({
</select>
</label>
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningDetectionQa || !selectedDetectionRunId || !detectionReferenceDatasetId}>
Compare detections to reference
Vergelijk met referentielaag
</button>
{detectionQaError ? (
<div className="result-state result-state-error">
<strong>Detection QA failed.</strong>
<strong>De kwaliteitscontrole is mislukt.</strong>
<p>{detectionQaError}</p>
</div>
) : null}
{detectionQaResult ? (
<div className="result-summary-card">
<p>Status: {detectionQaResult.status}</p>
<p>Quality check: {detectionQaResult.quality_check_id}</p>
<p>Precision: {detectionQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
<p>Kwaliteitscontrole: {detectionQaResult.quality_check_id}</p>
<p>Precisie: {detectionQaResult.precision?.toFixed(3) ?? 'n.v.t.'}</p>
<p>Recall: {detectionQaResult.recall?.toFixed(3) ?? 'n/a'}</p>
<p>F1: {detectionQaResult.f1_score?.toFixed(3) ?? 'n/a'}</p>
<p>Mean IoU: {detectionQaResult.mean_iou?.toFixed(3) ?? 'n/a'}</p>
<p>False positives: {detectionQaResult.false_positives}</p>
<p>False negatives: {detectionQaResult.false_negatives}</p>
<p>Gemiddelde overlap: {detectionQaResult.mean_iou?.toFixed(3) ?? 'n.v.t.'}</p>
<p>Fout positief: {detectionQaResult.false_positives}</p>
<p>Fout negatief: {detectionQaResult.false_negatives}</p>
{detectionQaResult.coverage ? (
<div className="detection-qa-diagnostic">
<span>Inference coverage</span>
@@ -1004,6 +1017,8 @@ export function DetectionLab({
</div>
) : null}
</div>
</div>
</details>
</section>
)
}
@@ -326,27 +326,27 @@ export function ExportCenter({
<div className="export-state-stack">
{loadingExports ? (
<div className="result-state result-state-loading">
<strong>Loading export registry.</strong>
<p>Retrieving persisted artifacts for the selected project.</p>
<strong>Downloads laden.</strong>
<p>De bewaarde bestanden van deze werkruimte worden opgehaald.</p>
</div>
) : null}
{exportError ? (
<div className="result-state result-state-error">
<strong>Export action failed.</strong>
<strong>Download kon niet worden gemaakt.</strong>
<p>{exportError}</p>
</div>
) : null}
{latestExport ? (
<div className="latest-export-card">
<span>Latest export</span>
<span>Laatste download</span>
<strong>{formatExportType(latestExport.export_type)}</strong>
<p>{latestExport.path}</p>
</div>
) : null}
{exports.length === 0 ? (
<div className="result-state result-state-empty">
<strong>No exports registered yet.</strong>
<p>Create metadata, GeoJSON or HTML report artifacts once the active project has data to hand off.</p>
<strong>Nog geen downloads gemaakt.</strong>
<p>Kies hierboven een leesbaar rapport, projectoverzicht of kaartlaag.</p>
</div>
) : null}
</div>
+1 -1
View File
@@ -1049,7 +1049,7 @@ export function MapWorkspace({
) : null}
<label className="geo-scope-select">
Gemeente of volledige regio
Snel naar een gemeente (optioneel)
<select aria-label="Werkgebied" value={selectedMapAreaId} onChange={(event) => handleSelectMapArea(event.target.value)} disabled={areas.length === 0}>
{areas.map((area) => (
<option key={area.id} value={area.id}>{area.name}</option>
+55
View File
@@ -1529,6 +1529,7 @@ details.ai-lab-model-surface > summary strong {
.technical-run-list,
.area-catalog-disclosure,
.dataset-history-disclosure,
.dataset-technical-details,
.quality-advanced-disclosure,
.provider-technical-details,
@@ -1543,6 +1544,7 @@ details.ai-lab-model-surface > summary strong {
.technical-run-list > summary,
.area-catalog-disclosure > summary,
.dataset-history-disclosure > summary,
.dataset-technical-details > summary,
.quality-advanced-disclosure > summary,
.provider-technical-details > summary,
@@ -1564,6 +1566,7 @@ details.ai-lab-model-surface > summary strong {
.technical-run-list > summary::-webkit-details-marker,
.area-catalog-disclosure > summary::-webkit-details-marker,
.dataset-history-disclosure > summary::-webkit-details-marker,
.dataset-technical-details > summary::-webkit-details-marker,
.quality-advanced-disclosure > summary::-webkit-details-marker,
.provider-technical-details > summary::-webkit-details-marker,
@@ -1575,6 +1578,7 @@ details.ai-lab-model-surface > summary strong {
.technical-run-list > summary::after,
.area-catalog-disclosure > summary::after,
.dataset-history-disclosure > summary::after,
.dataset-technical-details > summary::after,
.quality-advanced-disclosure > summary::after,
.provider-technical-details > summary::after,
@@ -1589,6 +1593,7 @@ details.ai-lab-model-surface > summary strong {
.technical-run-list[open] > summary::after,
.area-catalog-disclosure[open] > summary::after,
.dataset-history-disclosure[open] > summary::after,
.dataset-technical-details[open] > summary::after,
.quality-advanced-disclosure[open] > summary::after,
.provider-technical-details[open] > summary::after,
@@ -1645,6 +1650,56 @@ details.ai-lab-model-surface > summary strong {
margin-top: 0.25rem;
}
.dataset-history-disclosure {
margin-top: 0.75rem;
}
.dataset-history-disclosure > summary strong {
margin-right: 0.25rem;
color: var(--muted);
font-size: 0.75rem;
}
.dataset-history-list {
display: grid;
max-height: 24rem;
margin: 0;
overflow: auto;
padding: 0.5rem;
list-style: none;
background: #ffffff;
}
.dataset-history-list li {
display: flex;
min-width: 0;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--line);
padding: 0.65rem 0.25rem;
}
.dataset-history-list li:first-child {
border-top: 0;
}
.dataset-history-list li > div:first-child {
display: grid;
min-width: 0;
gap: 0.15rem;
}
.dataset-history-list li span {
color: var(--muted);
font-size: 0.76rem;
}
.detection-technical-evaluation {
margin-top: 0.75rem;
}
.ai-user-summary {
order: 0;
display: grid;