import { useEffect, useMemo, useState } from 'react' import './styles/app.css' import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel' import { DatasetPanel } from './components/datasets/DatasetPanel' import { DetectionLab } from './components/detection/DetectionLab' import { ExportCenter } from './components/exports/ExportCenter' import { ExportPreview } from './components/exports/ExportPreview' import { WorkbenchInspector } from './components/inspector/WorkbenchInspector' import { MapWorkspace } from './components/map/MapWorkspace' import { AreaPanel } from './components/project/AreaPanel' import { ProjectPanel } from './components/project/ProjectPanel' import { QualityResultsPanel } from './components/quality/QualityResultsPanel' import { WorkbenchStatusStrip } from './components/WorkbenchStatusStrip' import type { DatasetCreateResponse } from './types' import { ProviderPanel } from './components/providers/ProviderPanel' import { SegmentationLab } from './components/segmentation/SegmentationLab' import { useChangeDetectionWorkflow } from './hooks/useChangeDetectionWorkflow' import { useDemoWorkflow } from './hooks/useDemoWorkflow' import { useDetectionWorkflow } from './hooks/useDetectionWorkflow' import { useDatasetWorkflow } from './hooks/useDatasetWorkflow' import { useExportWorkflow } from './hooks/useExportWorkflow' import { useMapSelectionDataset } from './hooks/useMapSelectionDataset' import { useMapSelectionQa } from './hooks/useMapSelectionQa' import { useMapWorkspaceState } from './hooks/useMapWorkspaceState' import { useMapSelectionExtract } from './hooks/useMapSelectionExtract' import { useProviderCapabilities } from './hooks/useProviderCapabilities' import { useProjectWorkspace } from './hooks/useProjectWorkspace' import { useQualityWorkflow } from './hooks/useQualityWorkflow' import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow' import { useWorkbenchBootstrap } from './hooks/useWorkbenchBootstrap' function isVectorDatasetType(datasetType: string): boolean { return datasetType === 'vector' || datasetType === 'geojson' } type WorkspaceKey = 'overview' | 'data' | 'map' | 'analysis' | 'ai' | 'exports' | 'system' const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [ { key: 'overview', label: 'Overview', description: 'V1 readiness and next action' }, { key: 'data', label: 'Data', description: 'Projects, AOI and datasets' }, { key: 'map', label: 'Map', description: 'Layer review and inspection' }, { key: 'analysis', label: 'QA/QC', description: 'Quality checks and change detection' }, { key: 'ai', label: 'AI Labs', description: 'Detection and segmentation' }, { key: 'exports', label: 'Exports', description: 'Artifacts and downloads' }, { key: 'system', label: 'System', description: 'Provider capabilities' }, ] function App(): JSX.Element { const [activeWorkspace, setActiveWorkspace] = useState('overview') useEffect(() => { window.scrollTo({ top: 0, left: 0 }) }, [activeWorkspace]) const { projects, selectedProject, selectedProjectId, areas, datasets, loadingProjects, loadingAreas, loadingDatasets, errorMessage, projectForm, areaForm, loadProjects, loadProjectData, createProject, createArea, resetProjectData, setSelectedProjectId, setErrorMessage, setProjectForm, setAreaForm, } = useProjectWorkspace() const { selectedDatasetId, selectedDataset, selectedDatasetSummary, selectedRasterMetadata, selectedRasterStats, datasetContent, jobs, rasterPreview, latestRasterTileManifestPath, latestRasterTileManifest, selectedIntersectTargetId, selectedClipAreaId, rasterTileSize, rasterTileOverlap, rasterTileOutputName, rasterReprojectCrs, rasterReprojectResampling, ndviNirBand, ndviRedBand, ndwiGreenBand, ndwiNirBand, ndbiSwirBand, ndbiNirBand, loadingDatasetDetails, datasetDetailError, datasetForm, isRasterTileInputValid, loadDatasetJobs, loadDatasetDetails, uploadDataset, runVectorClip, runVectorBuffer, runVectorIntersect, runRasterInspect, runRasterPreview, runRasterStats, runRasterReproject, runRasterClip, runRasterTile, runRasterNdvi, runRasterNdwi, runRasterNdbi, pickDerivedDataset, refreshMetadata, resetDatasetForProject, setSelectedDatasetId, setSelectedIntersectTargetId, setSelectedClipAreaId, setRasterTileSize, setRasterTileOverlap, setRasterTileOutputName, setRasterReprojectCrs, setRasterReprojectResampling, setNdviNirBand, setNdviRedBand, setNdwiGreenBand, setNdwiNirBand, setNdbiSwirBand, setNdbiNirBand, setDatasetForm, } = useDatasetWorkflow({ selectedProjectId, areas, datasets, loadProjectData, setErrorMessage, isVectorDatasetType, }) const rasterUnavailableMessage = useMemo(() => { if (!selectedDataset || selectedDataset.dataset_type !== 'raster' || !selectedDataset.metadata_json) { return null } const metadata = selectedDataset.metadata_json as Record const processingCode = metadata['processing_code'] if (processingCode === 'RASTER_PROCESSING_UNAVAILABLE') { return String(metadata['processing_error'] ?? 'Raster processing unavailable.') } return null }, [selectedDataset]) const availableVectorTargets = useMemo( () => datasets.filter((item) => item.id !== selectedDatasetId && isVectorDatasetType(item.dataset_type)), [datasets, selectedDatasetId], ) const availableVectorDatasets = useMemo(() => datasets.filter((item) => isVectorDatasetType(item.dataset_type)), [datasets]) const availableMapDatasets = useMemo( () => datasets.filter((dataset) => isVectorDatasetType(dataset.dataset_type) && dataset.status === 'ready'), [datasets], ) const referenceDatasets = useMemo( () => availableVectorDatasets.filter((item) => item.dataset_role === 'reference'), [availableVectorDatasets], ) const candidateDatasets = useMemo( () => availableVectorDatasets.filter((item) => item.dataset_role !== 'reference'), [availableVectorDatasets], ) const rasterDatasets = useMemo(() => datasets.filter((item) => item.dataset_type === 'raster'), [datasets]) const { providers, loadingCapabilities, capabilitiesError, loadCapabilities, } = useProviderCapabilities() const { changeSourceDatasetId, changeTargetDatasetId, changeIouThreshold, changeIncludeUnchanged, runningChangeDetection, changeDetectionResult, changeDetectionError, runChangeDetection, setChangeSourceDatasetId, setChangeTargetDatasetId, setChangeIouThreshold, setChangeIncludeUnchanged, } = useChangeDetectionWorkflow({ selectedProjectId, availableVectorDatasets, loadDatasetJobs, }) const { qaCandidateDatasetId, qaReferenceDatasetId, qaAreaId, qaIouThreshold, qaRunning, qaResult, qaError, qualityChecks, qualityChecksError, qualityEvidenceGeoJson, qualityEvidenceLoading, qualityEvidenceError, loadQualityChecks, loadQualityEvidenceGeoJson, clearQualityEvidenceGeoJson, runQaComparison, setQaCandidateDatasetId, setQaReferenceDatasetId, setQaAreaId, setQaIouThreshold, } = useQualityWorkflow({ selectedProjectId, loadProjectData, }) const { detectionModels, modelAssets, loadingDetectionModels, detectionModelError, modelAssetError, selectedDetectionDatasetId, selectedDetectionModelId, selectedModelAssetId, detectionTileManifestPath, detectionConfidenceThreshold, runningDetection, detectionRunResult, detectionRunError, detectionRuns, selectedDetectionRunId, detectionItems, detectionGeoJson, detectionClassFilter, detectionMinConfidenceFilter, loadingDetectionResults, detectionReferenceDatasetId, detectionQaResult, detectionQaError, runningDetectionQa, yoloPreflight, loadingYoloPreflight, yoloPreflightError, calibrationThresholdText, runningDetectionCalibration, detectionCalibrationRows, detectionCalibrationError, loadDetectionModels, loadYoloPreflight, loadDetectionRuns, loadDetectionResults, runDetection, runDetectionQa, runDetectionCalibration, resetDetectionForProject, setSelectedDetectionDatasetId, setSelectedDetectionModelId, setSelectedModelAssetId, setDetectionTileManifestPath, setDetectionConfidenceThreshold, setSelectedDetectionRunId, setDetectionClassFilter, setDetectionMinConfidenceFilter, setDetectionReferenceDatasetId, setCalibrationThresholdText, } = useDetectionWorkflow({ selectedProjectId, rasterDatasets, qaIouThreshold, loadProjectData, loadQualityChecks, }) const useRasterTileManifestForDetection = () => { const manifestPath = latestRasterTileManifestPath.trim() if (!manifestPath || !selectedDataset || selectedDataset.dataset_type !== 'raster') { return } setDetectionTileManifestPath(manifestPath) setSelectedDetectionDatasetId(selectedDataset.id) setSelectedDetectionModelId('yolo-configured') setDetectionConfidenceThreshold(0.25) loadYoloPreflight(manifestPath).catch(() => null) setActiveWorkspace('ai') } const { segmentationModels, loadingSegmentationModels, segmentationModelError, selectedSegmentationDatasetId, selectedSegmentationModelId, selectedSegmentationModel, segmentationTileManifestPath, segmentationConfidenceThreshold, runningSegmentation, segmentationRunResult, segmentationRunError, segmentationRuns, selectedSegmentationRunId, segmentationItems, segmentationGeoJson, segmentationClassFilter, segmentationMinConfidenceFilter, loadingSegmentationResults, segmentationReferenceDatasetId, segmentationQaResult, segmentationQaError, runningSegmentationQa, loadSegmentationModels, loadSegmentationRuns, loadSegmentationResults, runSegmentation, runSegmentationQa, resetSegmentationForProject, setSelectedSegmentationDatasetId, setSelectedSegmentationModelId, setSegmentationTileManifestPath, setSegmentationConfidenceThreshold, setSelectedSegmentationRunId, setSegmentationClassFilter, setSegmentationMinConfidenceFilter, setSegmentationReferenceDatasetId, } = useSegmentationWorkflow({ selectedProjectId, rasterDatasets, qaIouThreshold, loadProjectData, loadQualityChecks, }) const useRasterTileManifestForSegmentation = () => { const manifestPath = latestRasterTileManifestPath.trim() if (!manifestPath || !selectedDataset || selectedDataset.dataset_type !== 'raster') { return } setSegmentationTileManifestPath(manifestPath) setSelectedSegmentationDatasetId(selectedDataset.id) setActiveWorkspace('ai') } const { exports, latestExport, exportError, loadingExports, exporting, selectionExporting, selectionExportError, latestSelectionExport, exportPreview, loadExports, exportSelectedDatasetGeoJson, exportSelectedDetectionRunGeoJson, exportSelectedSegmentationRunGeoJson, exportMapSelectionGeoJson, exportProjectMetadata, exportProjectReport, previewExportContent, downloadExportArtifact, resetExportsForProject, } = useExportWorkflow({ selectedProjectId, selectedDataset, selectedDetectionRunId, selectedSegmentationRunId, isVectorDatasetType, }) const { mapLayerVisible, mapLayerOpacity, selectedMapAreaId, areaLayerVisible, areaLayerOpacity, selectedMapFeature, areaFeatureCollection, mapFeatureCollection, mapLayerLabel, mapFeatureCount, areaFeatureCount, setMapLayerVisible, setMapLayerOpacity, setSelectedMapAreaId, setAreaLayerVisible, setAreaLayerOpacity, setSelectedMapFeature, } = useMapWorkspaceState({ areas, changeDetectionGeoJson: changeDetectionResult?.geojson ?? null, segmentationGeoJson, detectionGeoJson, datasetContent, selectedDataset, }) const { mapSelectionBbox, mapSelectionResult, mapSelectionLoading, mapSelectionError, runMapSelectionExtract, resetMapSelectionExtract, setMapSelectionBbox, } = useMapSelectionExtract({ selectedProjectId, selectedDataset, isVectorDatasetType, }) const { selectionDatasetSaving, selectionDatasetError, latestSelectionDataset, deriveMapSelectionDataset, } = useMapSelectionDataset({ selectedProjectId, selectedDataset, isVectorDatasetType, loadProjectData, loadDatasetDetails, setMapLayerVisible, }) const { selectedMapQaReferenceDatasetId, mapSelectionQaRunning, mapSelectionQaError, mapSelectionQaResult, latestMapSelectionQualityCheckId, runMapSelectionQa, setSelectedMapQaReferenceDatasetId, } = useMapSelectionQa({ selectedProjectId, latestSelectionDataset, loadQualityChecks, loadProjectData, }) const openMapSelectionQualityEvidence = () => { setActiveWorkspace('analysis') } const openQualityEvidenceOnMap = async (qualityCheckId: string) => { const result = await loadQualityEvidenceGeoJson(qualityCheckId) if (result) { setMapLayerVisible(true) setActiveWorkspace('map') } } const { loadingDemoWorkflow, demoWorkflowMessage, loadDemoWorkflow, } = useDemoWorkflow({ loadProjects, loadProjectData, loadDatasetDetails, loadDetectionRuns, loadSegmentationRuns, loadQualityChecks, loadExports, setSelectedProjectId, setSelectedDatasetId, setSelectedMapAreaId, setQaCandidateDatasetId, setQaReferenceDatasetId, setQaAreaId, setSelectedDetectionDatasetId, setDetectionReferenceDatasetId, setSelectedSegmentationDatasetId, setSegmentationReferenceDatasetId, setErrorMessage, }) useWorkbenchBootstrap({ selectedProjectId, selectedDetectionRunId, detectionClassFilter, detectionMinConfidenceFilter, selectedSegmentationRunId, segmentationClassFilter, segmentationMinConfidenceFilter, loadProjects, loadCapabilities, loadDetectionModels, loadSegmentationModels, loadProjectData, loadDetectionRuns, loadSegmentationRuns, loadQualityChecks, loadExports, loadDetectionResults, loadSegmentationResults, resetProjectData, resetDatasetForProject, resetDetectionForProject, resetSegmentationForProject, resetExportsForProject, }) const selectedArea = areas.find((area) => area.id === selectedMapAreaId) ?? null const activeWorkspaceItem = workspaceNavItems.find((item) => item.key === activeWorkspace) ?? workspaceNavItems[0] const mapLayerSourceLabel = useMemo(() => { if (changeDetectionResult?.geojson) { return 'Change detection' } if (segmentationGeoJson) { return 'Segmentation run' } if (detectionGeoJson) { return 'Detection run' } if (datasetContent && selectedDataset) { return `${selectedDataset.dataset_type} dataset` } return 'No active vector or result layer' }, [changeDetectionResult?.geojson, datasetContent, detectionGeoJson, segmentationGeoJson, selectedDataset]) const mapLayerProvenance = useMemo(() => { if (changeDetectionResult?.geojson) { return `source ${changeSourceDatasetId || 'n/a'} -> target ${changeTargetDatasetId || 'n/a'}` } if (segmentationGeoJson) { return selectedSegmentationRunId ? `analysis run ${selectedSegmentationRunId}` : 'segmentation results loaded' } if (detectionGeoJson) { return selectedDetectionRunId ? `analysis run ${selectedDetectionRunId}` : 'detection results loaded' } if (datasetContent && selectedDataset) { return `${selectedDataset.dataset_role ?? 'source'} / ${selectedDataset.source_name ?? selectedDataset.source}` } return 'Open a dataset, detection run, segmentation run or change result to draw it here.' }, [ changeDetectionResult?.geojson, changeSourceDatasetId, changeTargetDatasetId, datasetContent, detectionGeoJson, segmentationGeoJson, selectedDataset, selectedDetectionRunId, selectedSegmentationRunId, ]) const openDatasetInMap = (dataset: DatasetCreateResponse) => { if (selectedProjectId) { loadDatasetDetails(selectedProjectId, dataset) } setMapLayerVisible(true) setActiveWorkspace('map') } const openDatasetExport = (dataset: DatasetCreateResponse) => { if (selectedProjectId) { loadDatasetDetails(selectedProjectId, dataset) } setActiveWorkspace('exports') } const openWorkflowGuidanceStep = (target: WorkspaceKey) => { if (target === 'map' && availableMapDatasets.length > 0 && !mapFeatureCollection) { openDatasetInMap(availableMapDatasets[0]) return } if (target === 'exports' && availableMapDatasets.length > 0 && !selectedDataset) { openDatasetExport(availableMapDatasets[0]) return } setActiveWorkspace(target) } const hasAnalysisOutput = qualityChecks.length > 0 || Boolean(changeDetectionResult) || detectionItems.length > 0 || segmentationItems.length > 0 const hasMapContext = mapFeatureCount > 0 || areaFeatureCount > 0 const workflowGuidanceComplete = Boolean(selectedProjectId) && datasets.length > 0 && hasMapContext && hasAnalysisOutput && exports.length > 0 const recommendedWorkflowTarget: WorkspaceKey = !selectedProjectId ? 'data' : datasets.length === 0 ? 'data' : !hasMapContext ? 'map' : !hasAnalysisOutput ? 'analysis' : exports.length === 0 ? 'exports' : 'exports' const workflowGuidanceSteps: Array<{ step: string title: string detail: string status: string ready: boolean target: WorkspaceKey }> = [ { step: '1', title: 'Project & AOI', detail: selectedProjectId ? `${areas.length} AOI record${areas.length === 1 ? '' : 's'} available` : 'Create or load a project context', status: selectedProjectId ? 'ready' : 'next', ready: Boolean(selectedProjectId), target: 'data', }, { step: '2', title: 'Data', detail: datasets.length > 0 ? `${datasets.length} dataset${datasets.length === 1 ? '' : 's'} loaded` : 'Upload source and reference datasets', status: datasets.length > 0 ? 'ready' : 'waiting', ready: datasets.length > 0, target: 'data', }, { step: '3', title: 'Map', detail: hasMapContext ? mapFeatureCount > 0 ? `${mapFeatureCount} layer feature${mapFeatureCount === 1 ? '' : 's'}${areaFeatureCount > 0 ? ' + AOI' : ''}` : 'AOI loaded' : 'Inspect AOI and selected layer', status: hasMapContext ? 'ready' : 'waiting', ready: hasMapContext, target: 'map', }, { step: '4', title: 'QA / AI', detail: hasAnalysisOutput ? 'QA, change, detection or segmentation output exists' : 'Run checks after data is ready', status: hasAnalysisOutput ? 'ready' : 'waiting', ready: hasAnalysisOutput, target: 'analysis', }, { step: '5', title: 'Export', detail: exports.length > 0 ? `${exports.length} export artifact${exports.length === 1 ? '' : 's'} recorded` : 'Package validated project outputs', status: exports.length > 0 ? 'ready' : 'waiting', ready: exports.length > 0, target: 'exports', }, ] return (
Skip to workspace

GeoAI Workbench

GeoIntel Kempen

Project {selectedProject?.name ?? 'No project'}
AOI {selectedArea?.name ?? (areas.length > 0 ? 'Select area' : 'No AOI')}
Dataset {selectedDataset?.name ?? (datasets.length > 0 ? 'Select dataset' : 'No dataset')}
Layer {mapFeatureCollection ? `${mapFeatureCount} features` : 'No active layer'}
{errorMessage ?

{errorMessage}

: null}

Workspace

{activeWorkspaceItem.label}

{activeWorkspaceItem.description}

{workspaceNavItems.slice(0, 5).map((item) => ( ))}
{selectedProject ? `Active project: ${selectedProject.name}` : 'Start with a project or load the demo workflow.'}
{activeWorkspace === 'overview' ? (

Workbench flow

Project to export path

{workflowGuidanceComplete ? 'Ready for handoff' : `Next: ${workspaceNavItems.find((item) => item.key === recommendedWorkflowTarget)?.label ?? 'Overview'}`}
{workflowGuidanceSteps.map((step) => ( ))}

Recommended flow

Build the project, load data, review on map, validate, export.

Keep the daily workflow focused: prepare data first, inspect it spatially, then run QA/QC or AI lab tasks only when the required datasets are present.

) : null} {activeWorkspace === 'data' ? (
) : null} {activeWorkspace === 'map' ? ( ) : null} {activeWorkspace === 'analysis' ? (
loadQualityChecks()} onOpenEvidenceMap={openQualityEvidenceOnMap} evidenceLoading={qualityEvidenceLoading} evidenceError={qualityEvidenceError} />
) : null} {activeWorkspace === 'ai' ? (
loadYoloPreflight()} onSelectDataset={setSelectedDetectionDatasetId} onSelectModel={setSelectedDetectionModelId} onSelectModelAsset={setSelectedModelAssetId} onSetConfidenceThreshold={setDetectionConfidenceThreshold} onSetTileManifestPath={setDetectionTileManifestPath} onRunDetection={runDetection} onLoadRuns={() => loadDetectionRuns()} onSelectRun={setSelectedDetectionRunId} onSetClassFilter={setDetectionClassFilter} onSetMinConfidenceFilter={setDetectionMinConfidenceFilter} onLoadResults={() => loadDetectionResults()} onSelectReferenceDataset={setDetectionReferenceDatasetId} onRunQa={runDetectionQa} onSetCalibrationThresholdText={setCalibrationThresholdText} onRunCalibration={runDetectionCalibration} /> loadSegmentationRuns()} onSelectRun={setSelectedSegmentationRunId} onSetClassFilter={setSegmentationClassFilter} onSetMinConfidenceFilter={setSegmentationMinConfidenceFilter} onLoadResults={() => loadSegmentationResults()} onSelectReferenceDataset={setSegmentationReferenceDatasetId} onRunQa={runSegmentationQa} />
) : null} {activeWorkspace === 'exports' ? (
loadExports()} onExportDataset={exportSelectedDatasetGeoJson} onExportDetectionRun={exportSelectedDetectionRunGeoJson} onExportSegmentationRun={exportSelectedSegmentationRunGeoJson} onExportProjectMetadata={exportProjectMetadata} onExportProjectReport={exportProjectReport} onPreviewContent={previewExportContent} onDownload={downloadExportArtifact} />
) : null} {activeWorkspace === 'system' ? ( ) : null}
) } export default App