Шатурный Алексей Давыдович
add files
0269f70
import React, { useState, useMemo, useEffect, useCallback, lazy, Suspense } from 'react';
import { DatasetManager } from './components/DatasetManager';
import type { Dataset, DatasetMetadata } from './types';
import * as db from './services/dbService';
import * as apiService from './services/apiService';
import { Spinner } from './components/common/Spinner';
const ComparisonTool = lazy(() => import('./components/ComparisonTool').then(module => ({ default: module.ComparisonTool })));
type View = 'manager' | 'comparison';
const App: React.FC = () => {
const [datasets, setDatasets] = useState<DatasetMetadata[]>([]);
const [selectedDatasetId, setSelectedDatasetId] = useState<string | null>(null);
const [activeDataset, setActiveDataset] = useState<Dataset | null>(null);
const [view, setView] = useState<View>('manager');
const [isLoading, setIsLoading] = useState(true);
const [isNavigating, setIsNavigating] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadInitialData = async () => {
try {
const localMeta = await db.getAllDatasetMetadata();
let sharedMeta: DatasetMetadata[] = [];
try {
sharedMeta = await apiService.getSharedDatasetMetadata();
} catch (e) {
console.error("Could not load shared datasets, continuing with local.", e);
setError("Could not load cloud datasets. The backend service may be unavailable. Local datasets are still accessible.");
}
const allMeta = [...sharedMeta, ...localMeta];
setDatasets(allMeta);
if (allMeta.length > 0) {
// Select the most recent dataset by default
const sortedMeta = [...allMeta].sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime());
setSelectedDatasetId(sortedMeta[0].id);
}
} catch (error) {
console.error("Failed to load initial data", error);
setError("A critical error occurred while loading local datasets.");
} finally {
setIsLoading(false);
}
};
loadInitialData();
}, []);
const addDataset = async (newDataset: Dataset) => {
await db.addDataset(newDataset);
const localMeta = await db.getAllDatasetMetadata();
const sharedMeta = datasets.filter(d => d.isShared); // Keep existing shared meta
setDatasets([...sharedMeta, ...localMeta]);
setSelectedDatasetId(newDataset.id);
};
const deleteDataset = async (id: string) => {
await db.deleteDataset(id);
setDatasets(prevDatasets => {
const newDatasets = prevDatasets.filter(d => d.id !== id);
if (selectedDatasetId === id) {
const sortedMeta = [...newDatasets].sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime());
setSelectedDatasetId(sortedMeta.length > 0 ? sortedMeta[0].id : null);
}
return newDatasets;
});
};
const renameDataset = async (id: string, newName: string) => {
await db.renameDataset(id, newName);
setDatasets(prev => prev.map(d => d.id === id ? { ...d, name: newName } : d));
};
const processedDatasets = useMemo(() => {
return datasets.filter(d => d.processingState === 'processed');
}, [datasets]);
const getFullDataset = async (id: string): Promise<Dataset | null> => {
const meta = datasets.find(d => d.id === id);
if (!meta) return null;
if (meta.isShared) {
return apiService.getSharedDataset(id);
} else {
return db.getDataset(id);
}
};
const handleOpenComparisonTool = useCallback(async () => {
if (!selectedDatasetId) return;
const selectedMeta = datasets.find(d => d.id === selectedDatasetId);
if (!selectedMeta || selectedMeta.processingState !== 'processed') return;
setView('comparison');
setActiveDataset(null);
setIsNavigating(true);
try {
const fullDataset = await getFullDataset(selectedDatasetId);
if (!fullDataset) {
throw new Error(`Failed to load dataset ${selectedDatasetId}.`);
}
// *** NEW LOGIC ***
// If it's a local dataset, ensure it's in the backend's cache before proceeding.
if (!fullDataset.isShared) {
console.log("Local dataset selected. Ensuring it's cached on the backend...");
await apiService.ensureDatasetInCache(fullDataset);
console.log("Backend cache confirmed.");
}
setActiveDataset(fullDataset);
} catch (error) {
console.error("Error preparing comparison tool:", error);
alert(`Error: Could not load the selected dataset. ${error instanceof Error ? error.message : ''}`);
setView('manager'); // Go back on error
} finally {
setIsNavigating(false);
}
}, [selectedDatasetId, datasets]);
const handleDatasetChange = useCallback(async (newId: string) => {
setSelectedDatasetId(newId);
setActiveDataset(null);
setIsNavigating(true);
try {
const fullDataset = await getFullDataset(newId);
if (!fullDataset) {
throw new Error(`Failed to load dataset ${newId}.`);
}
// Also ensure cache is hydrated when switching datasets inside the tool
if (!fullDataset.isShared) {
await apiService.ensureDatasetInCache(fullDataset);
}
setActiveDataset(fullDataset);
} catch (error) {
console.error(`Error switching dataset to ${newId}:`, error);
setActiveDataset(null);
} finally {
setIsNavigating(false);
}
}, []);
const mainContent = () => {
if (isLoading) {
return <div className="flex justify-center items-center h-64"><Spinner /><span>Loading Datasets...</span></div>;
}
const errorBanner = error ? (
<div className="bg-red-800/50 border border-red-600 text-red-200 px-4 py-3 rounded-lg mb-4" role="alert">
<p>
<strong className="font-bold">Cloud Connection Error:</strong> {error}
</p>
</div>
) : null;
if (view === 'manager') {
return (
<DatasetManager
datasets={datasets}
selectedDatasetId={selectedDatasetId}
onSelectDataset={setSelectedDatasetId}
onAddDataset={addDataset}
onDeleteDataset={deleteDataset}
onRenameDataset={renameDataset}
onOpenComparisonTool={handleOpenComparisonTool}
onGetFullDataset={getFullDataset}
errorBanner={errorBanner}
/>
);
}
if (view === 'comparison') {
const fallbackUI = <div className="flex justify-center items-center h-64"><Spinner /><span>Loading Comparison Tool...</span></div>;
if (isNavigating || !activeDataset) {
return fallbackUI;
}
return (
<Suspense fallback={fallbackUI}>
<ComparisonTool
key={activeDataset.id}
dataset={activeDataset}
allDatasets={processedDatasets}
onDatasetChange={handleDatasetChange}
onBack={() => { setView('manager'); setActiveDataset(null); }}
/>
</Suspense>
);
}
return null;
};
return (
<div className="min-h-screen bg-gray-900 text-gray-200 font-sans">
<header className="bg-gray-800/50 backdrop-blur-sm border-b border-gray-700 sticky top-0 z-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between">
<h1 className="text-2xl font-bold text-cyan-400">
Cross-Modal Object Comparison Tool
</h1>
</div>
</header>
<main className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
{mainContent()}
</main>
</div>
);
};
export default App;