Spaces:
Running
Running
| 'use client'; | |
| import React, { useState } from 'react'; | |
| import { useValidationStore } from '@/store/validation-store'; | |
| import { WorkflowStepper, Step } from '@/components/common/workflow-stepper'; | |
| import { WorkflowNavigation } from '@/components/common/workflow-navigation'; | |
| import { UploadDropzone } from '@/features/upload/components/UploadDropzone'; | |
| import { MetadataOverview } from '@/features/metadata/components/metadata-overview'; | |
| import { ValidationViewer } from './validation-viewer'; | |
| import dynamic from 'next/dynamic'; | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; | |
| const DifferenceMapViewer = dynamic( | |
| () => import('@/features/comparison/components/difference-map-viewer').then(mod => mod.DifferenceMapViewer), | |
| { ssr: false, loading: () => <div className="w-full h-[500px] flex items-center justify-center animate-pulse bg-muted">Loading map...</div> } | |
| ); | |
| import { MetricsDashboard } from '@/features/metrics/components/metrics-dashboard'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Input } from '@/components/ui/input'; | |
| import { DifferenceMapData } from '@/features/comparison/types'; | |
| import { useMetadata } from '@/features/metadata/hooks/use-metadata'; | |
| import { useValidation } from '@/features/validation/hooks/use-validation'; | |
| import { MetadataSummary } from '@/features/metadata/components/metadata-summary'; | |
| import { MetadataVariableList } from '@/features/metadata/components/metadata-variable-list'; | |
| import { Loader2 } from 'lucide-react'; | |
| import { visualizationClient } from '@/lib/api/visualization-client'; | |
| const dummyDifferenceMap: DifferenceMapData = { | |
| id: 'dummy', | |
| type: 'T0.5', | |
| timestamp: new Date().toISOString(), | |
| band: 'TIR1', | |
| resolution: '1km', | |
| dimensions: [0, 0], | |
| data: [], | |
| min: 0, | |
| max: 0, | |
| meanDifference: 0, | |
| maxDifference: 0, | |
| minDifference: 0, | |
| stdDeviation: 0, | |
| similarityScore: 0 | |
| }; | |
| export function ValidationWorkflowWrapper() { | |
| const store = useValidationStore(); | |
| const { currentStep, nextStep, prevStep, setArtifactId, setGroundTruthFileId, setGroundTruthFilename } = store; | |
| const [tempArtifactId, setTempArtifactId] = useState(''); | |
| const { fetchValidationMetadata } = useMetadata(); | |
| const { alignFrames } = useValidation(); | |
| // React.useEffect(() => { | |
| // if (store.artifactId && store.currentStep === 1) { | |
| // store.nextStep(); | |
| // } | |
| // }, [store.artifactId, store.currentStep, store]); | |
| const handleArtifactLoad = () => { | |
| setArtifactId(tempArtifactId); | |
| nextStep(); | |
| }; | |
| const handleGroundTruthUpload = async (fileId: string, filename: string) => { | |
| setGroundTruthFileId(fileId); | |
| setGroundTruthFilename(filename); | |
| nextStep(); | |
| await fetchValidationMetadata(fileId); | |
| }; | |
| const renderMetadata = () => { | |
| if (store.metadataLoading) { | |
| return ( | |
| <div className="flex flex-col items-center justify-center p-12 text-muted-foreground"> | |
| <Loader2 className="w-8 h-8 animate-spin mb-4" /> | |
| <p>Extracting metadata...</p> | |
| </div> | |
| ); | |
| } | |
| if (store.metadataError) { | |
| return ( | |
| <div className="flex flex-col items-center justify-center p-12 text-destructive"> | |
| <p>Error extracting metadata: {store.metadataError}</p> | |
| </div> | |
| ); | |
| } | |
| if (!store.groundTruthMetadata) return null; | |
| const availableVariables = store.groundTruthMetadata.variables?.map(v => v.name) || ["C13", "TIR1"]; | |
| return ( | |
| <div className="space-y-6"> | |
| <MetadataOverview data={store.groundTruthMetadata} /> | |
| <MetadataSummary data={store.groundTruthMetadata} /> | |
| <MetadataVariableList data={store.groundTruthMetadata} /> | |
| <div className="flex flex-col items-center gap-4 pt-4 border-t mt-6"> | |
| <div className="w-full max-w-sm space-y-2"> | |
| <label className="text-sm font-medium">Select Variable for Validation</label> | |
| <Select | |
| value={store.selectedVariable || availableVariables[0]} | |
| onValueChange={(val) => store.setSelectedVariable(val)} | |
| > | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select variable..." /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {availableVariables.map((v) => ( | |
| <SelectItem key={v} value={v}>{v}</SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| <Button onClick={async () => { | |
| if (!store.selectedVariable) { | |
| store.setSelectedVariable(availableVariables[0]); | |
| } | |
| nextStep(); | |
| await alignFrames(); | |
| }}>Proceed to Visual Inspection</Button> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const steps: Step[] = [ | |
| { | |
| id: 1, | |
| label: 'Load Artifact', | |
| description: 'Fetch Generated T0.5', | |
| component: ( | |
| <div className="space-y-6 max-w-xl mx-auto flex flex-col items-center justify-center h-full min-h-[300px]"> | |
| <h2 className="text-xl font-semibold">Load Generated Frame</h2> | |
| <p className="text-muted-foreground text-center"> | |
| Enter the Artifact ID of the interpolated frame generated in the previous workflow. | |
| </p> | |
| <div className="flex w-full max-w-sm items-center space-x-2"> | |
| <Input | |
| type="text" | |
| placeholder="e.g. hf_artifact_9823" | |
| value={tempArtifactId} | |
| onChange={(e) => setTempArtifactId(e.target.value)} | |
| /> | |
| <Button onClick={handleArtifactLoad} disabled={!tempArtifactId}>Load</Button> | |
| </div> | |
| </div> | |
| ), | |
| }, | |
| { | |
| id: 2, | |
| label: 'Ground Truth', | |
| description: 'Upload Actual T0.5', | |
| component: ( | |
| <div className="space-y-6"> | |
| <UploadDropzone onUploadComplete={handleGroundTruthUpload} /> | |
| </div> | |
| ), | |
| }, | |
| { | |
| id: 3, | |
| label: 'Metadata', | |
| description: 'Review Observation', | |
| component: renderMetadata(), | |
| }, | |
| { | |
| id: 4, | |
| label: 'Slider', | |
| description: 'AI vs Ground Truth', | |
| component: ( | |
| <div className="space-y-6"> | |
| {store.validationLoading ? ( | |
| <div className="flex flex-col items-center justify-center p-12 text-muted-foreground h-[500px] border rounded-lg"> | |
| <Loader2 className="w-8 h-8 animate-spin mb-4" /> | |
| <p>Aligning frames and validating coordinates...</p> | |
| </div> | |
| ) : store.validationError ? ( | |
| <div className="flex flex-col items-center justify-center p-12 text-destructive h-[500px] border rounded-lg border-destructive/50 bg-destructive/5"> | |
| <p>Validation Failed: {store.validationError}</p> | |
| <Button variant="outline" className="mt-4" onClick={alignFrames}>Retry Alignment</Button> | |
| </div> | |
| ) : ( | |
| <ValidationViewer /> | |
| )} | |
| <div className="flex justify-center pt-4"> | |
| <Button onClick={nextStep} disabled={store.validationLoading || !store.validationPair}>Analyze Difference Map</Button> | |
| </div> | |
| </div> | |
| ), | |
| }, | |
| { | |
| id: 5, | |
| label: 'Difference', | |
| description: 'Spatial Error Map', | |
| component: ( | |
| <div className="space-y-6"> | |
| <DifferenceMapViewer | |
| differenceMap={store.differenceMap || dummyDifferenceMap} | |
| errorMapUrl={ | |
| store.validationPair | |
| ? visualizationClient.getErrorMapLayerUrl(store.validationPair.groundTruthId, store.validationPair.generatedId, store.selectedVariable || "C13", 0) | |
| : null | |
| } | |
| fileIdForBounds={store.validationPair?.groundTruthId} | |
| variable={store.selectedVariable || "C13"} | |
| isFullscreen={false} | |
| /> | |
| <div className="flex justify-center pt-4"> | |
| <Button onClick={nextStep}>Compute Metrics</Button> | |
| </div> | |
| </div> | |
| ), | |
| }, | |
| { | |
| id: 6, | |
| label: 'Metrics', | |
| description: 'Quantitative Analysis', | |
| component: ( | |
| <div className="space-y-6"> | |
| <MetricsDashboard /> | |
| <div className="flex justify-center gap-4 pt-4"> | |
| <Button variant="outline" onClick={() => { | |
| store.reset(); | |
| window.location.reload(); | |
| }}> | |
| Start New Validation | |
| </Button> | |
| </div> | |
| </div> | |
| ), | |
| }, | |
| ]; | |
| return ( | |
| <div className="w-full max-w-6xl mx-auto py-8"> | |
| <WorkflowStepper steps={steps} currentStep={currentStep} /> | |
| <WorkflowNavigation | |
| currentStep={currentStep} | |
| totalSteps={steps.length} | |
| onPrev={prevStep} | |
| /> | |
| </div> | |
| ); | |
| } | |