Spaces:
Running
Running
| import React, { useState, useEffect } from "react"; | |
| import { Card, CardContent } from "@/components/ui/card"; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; | |
| import { GitCompare, BarChart3, Eye } from "lucide-react"; | |
| import { api } from "@/lib/api"; | |
| import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; | |
| import { GraphSelector } from "./GraphSelector"; | |
| import { ComparisonResults } from "./ComparisonResults"; | |
| import { VisualComparison } from "./VisualComparison"; | |
| import { AvailableGraph, GraphComparisonResults } from "@/types"; | |
| export const GraphComparisonView: React.FC = () => { | |
| const [activeTab, setActiveTab] = useState("selection"); | |
| const [availableGraphs, setAvailableGraphs] = useState<AvailableGraph[]>([]); | |
| const [selectedGraph1, setSelectedGraph1] = useState<AvailableGraph | null>( | |
| null | |
| ); | |
| const [selectedGraph2, setSelectedGraph2] = useState<AvailableGraph | null>( | |
| null | |
| ); | |
| const [comparisonResults, setComparisonResults] = | |
| useState<GraphComparisonResults | null>(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| // Load available graphs on component mount | |
| useEffect(() => { | |
| loadAvailableGraphs(); | |
| }, []); | |
| // Auto-refresh available graphs every 15 seconds | |
| useEffect(() => { | |
| const interval = setInterval(() => { | |
| if (!isLoading) { | |
| loadAvailableGraphs(); | |
| } | |
| }, 15000); // 15 seconds | |
| return () => clearInterval(interval); | |
| }, [isLoading]); | |
| const loadAvailableGraphs = async () => { | |
| try { | |
| setIsLoading(true); | |
| setError(null); | |
| const response = await api.graphComparison.listAvailableGraphs(); | |
| setAvailableGraphs(response.final_graphs); | |
| } catch (err) { | |
| setError(err instanceof Error ? err.message : "Failed to load graphs"); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const handleGraphSelection = ( | |
| graph1: AvailableGraph | null, | |
| graph2: AvailableGraph | null | |
| ) => { | |
| setSelectedGraph1(graph1); | |
| setSelectedGraph2(graph2); | |
| // Remove auto-navigation - let users control when to switch tabs | |
| }; | |
| const handleCompareGraphs = async () => { | |
| if (!selectedGraph1 || !selectedGraph2) return; | |
| try { | |
| setIsLoading(true); | |
| setError(null); | |
| const results = await api.graphComparison.compareGraphs( | |
| selectedGraph1.id, | |
| selectedGraph2.id, | |
| { similarity_threshold: 0.7, use_cache: true } | |
| ); | |
| setComparisonResults(results); | |
| setActiveTab("results"); | |
| } catch (err) { | |
| setError(err instanceof Error ? err.message : "Failed to compare graphs"); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const canCompare = selectedGraph1 && selectedGraph2; | |
| return ( | |
| <div className="flex flex-col h-screen bg-background"> | |
| {/* Error Display */} | |
| {error && ( | |
| <div className="mx-4 mt-4 p-4 bg-destructive/10 border border-destructive/20 rounded-lg"> | |
| <p className="text-destructive text-sm">{error}</p> | |
| </div> | |
| )} | |
| {/* Content */} | |
| <div className="flex-1 overflow-hidden"> | |
| <Tabs | |
| value={activeTab} | |
| onValueChange={setActiveTab} | |
| className="h-full flex flex-col" | |
| > | |
| <div className="border-b enhanced-tabs px-4"> | |
| <TabsList className="grid w-full grid-cols-3 h-10 bg-transparent p-1 gap-1"> | |
| <TabsTrigger | |
| value="selection" | |
| className="flex items-center gap-2 h-8 px-3 rounded-md text-sm font-medium transition-all duration-200 data-[state=active]:bg-white data-[state=active]:text-primary data-[state=active]:shadow-sm data-[state=active]:border data-[state=active]:border-primary/20 hover:bg-white/60 hover:text-foreground text-muted-foreground" | |
| > | |
| <GitCompare className="h-3.5 w-3.5" /> | |
| Selection | |
| </TabsTrigger> | |
| <TabsTrigger | |
| value="results" | |
| disabled={!comparisonResults} | |
| className="flex items-center gap-2 h-8 px-3 rounded-md text-sm font-medium transition-all duration-200 data-[state=active]:bg-white data-[state=active]:text-primary data-[state=active]:shadow-sm data-[state=active]:border data-[state=active]:border-primary/20 hover:bg-white/60 hover:text-foreground text-muted-foreground disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| <BarChart3 className="h-3.5 w-3.5" /> | |
| Results | |
| </TabsTrigger> | |
| <TabsTrigger | |
| value="visual" | |
| disabled={!canCompare} | |
| className="flex items-center gap-2 h-8 px-3 rounded-md text-sm font-medium transition-all duration-200 data-[state=active]:bg-white data-[state=active]:text-primary data-[state=active]:shadow-sm data-[state=active]:border data-[state=active]:border-primary/20 hover:bg-white/60 hover:text-foreground text-muted-foreground disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| <Eye className="h-3.5 w-3.5" /> | |
| Visual | |
| </TabsTrigger> | |
| </TabsList> | |
| </div> | |
| <div className="flex-1 overflow-y-auto"> | |
| <TabsContent value="selection" className="h-full m-0 p-4"> | |
| {isLoading && !availableGraphs.length ? ( | |
| <div className="flex items-center justify-center h-64"> | |
| <LoadingSpinner size="lg" /> | |
| </div> | |
| ) : ( | |
| <GraphSelector | |
| availableGraphs={availableGraphs} | |
| selectedGraph1={selectedGraph1} | |
| selectedGraph2={selectedGraph2} | |
| onSelectionChange={handleGraphSelection} | |
| onCompareGraphs={handleCompareGraphs} | |
| isLoading={isLoading} | |
| /> | |
| )} | |
| </TabsContent> | |
| <TabsContent value="results" className="h-full m-0 p-4"> | |
| {comparisonResults ? ( | |
| <ComparisonResults results={comparisonResults} /> | |
| ) : ( | |
| <Card> | |
| <CardContent className="p-8 text-center"> | |
| <p className="text-muted-foreground"> | |
| No comparison results available | |
| </p> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </TabsContent> | |
| <TabsContent value="visual" className="h-full m-0"> | |
| {canCompare ? ( | |
| <VisualComparison | |
| graph1={selectedGraph1!} | |
| graph2={selectedGraph2!} | |
| comparisonResults={comparisonResults} | |
| /> | |
| ) : ( | |
| <Card className="m-4"> | |
| <CardContent className="p-8 text-center"> | |
| <p className="text-muted-foreground"> | |
| Select 2 graphs to view visual comparison | |
| </p> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </TabsContent> | |
| </div> | |
| </Tabs> | |
| </div> | |
| </div> | |
| ); | |
| }; | |