Fill-the-Frames / frontend /src /features /validation /components /validation-workflow-wrapper.tsx
sid385's picture
feat(fullstack): enhance visual clarity and add restart workflows
7468f42
Raw
History Blame Contribute Delete
8.85 kB
'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>
);
}