Spaces:
Running
Running
Commit
·
9c5dc16
1
Parent(s):
b9fa64b
add
Browse files
frontend/src/components/features/comparison/ComparisonResults.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React from "react";
|
| 2 |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 3 |
import { Badge } from "@/components/ui/badge";
|
| 4 |
import { Progress } from "@/components/ui/progress";
|
|
@@ -8,8 +8,9 @@ import {
|
|
| 8 |
AccordionItem,
|
| 9 |
AccordionTrigger,
|
| 10 |
} from "@/components/ui/accordion";
|
| 11 |
-
import { BarChart3, Users, Network, GitBranch, TrendingUp } from "lucide-react";
|
| 12 |
-
import { GraphComparisonResults } from "@/types";
|
|
|
|
| 13 |
|
| 14 |
// Prefer system_name when available
|
| 15 |
const getGraphDisplayName = (g?: { filename: string; system_name?: string }) => {
|
|
@@ -27,6 +28,55 @@ export const ComparisonResults: React.FC<ComparisonResultsProps> = ({
|
|
| 27 |
const graph1_info = results.metadata?.graph1;
|
| 28 |
const graph2_info = results.metadata?.graph2;
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
// Get entity and relation counts from the actual metrics
|
| 31 |
const graph1_entity_count =
|
| 32 |
results.entity_metrics.unique_to_graph1 +
|
|
@@ -93,6 +143,29 @@ export const ComparisonResults: React.FC<ComparisonResultsProps> = ({
|
|
| 93 |
|
| 94 |
return (
|
| 95 |
<div className="space-y-6">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
{/* Header Summary */}
|
| 97 |
<Card>
|
| 98 |
<CardHeader>
|
|
@@ -262,6 +335,38 @@ export const ComparisonResults: React.FC<ComparisonResultsProps> = ({
|
|
| 262 |
className="h-2"
|
| 263 |
/>
|
| 264 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
</div>
|
| 266 |
</AccordionContent>
|
| 267 |
</AccordionItem>
|
|
@@ -344,6 +449,38 @@ export const ComparisonResults: React.FC<ComparisonResultsProps> = ({
|
|
| 344 |
className="h-2"
|
| 345 |
/>
|
| 346 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
</div>
|
| 348 |
</AccordionContent>
|
| 349 |
</AccordionItem>
|
|
|
|
| 1 |
+
import React, { useEffect, useMemo, useState } from "react";
|
| 2 |
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 3 |
import { Badge } from "@/components/ui/badge";
|
| 4 |
import { Progress } from "@/components/ui/progress";
|
|
|
|
| 8 |
AccordionItem,
|
| 9 |
AccordionTrigger,
|
| 10 |
} from "@/components/ui/accordion";
|
| 11 |
+
import { BarChart3, Users, Network, GitBranch, TrendingUp, Lightbulb } from "lucide-react";
|
| 12 |
+
import { GraphComparisonResults, GraphDetailsResponse } from "@/types";
|
| 13 |
+
import { api } from "@/lib/api";
|
| 14 |
|
| 15 |
// Prefer system_name when available
|
| 16 |
const getGraphDisplayName = (g?: { filename: string; system_name?: string }) => {
|
|
|
|
| 28 |
const graph1_info = results.metadata?.graph1;
|
| 29 |
const graph2_info = results.metadata?.graph2;
|
| 30 |
|
| 31 |
+
const [graph1Details, setGraph1Details] = useState<GraphDetailsResponse | null>(null);
|
| 32 |
+
const [graph2Details, setGraph2Details] = useState<GraphDetailsResponse | null>(null);
|
| 33 |
+
|
| 34 |
+
useEffect(() => {
|
| 35 |
+
const loadDetails = async () => {
|
| 36 |
+
try {
|
| 37 |
+
if (graph1_info?.id) setGraph1Details(await api.graphComparison.getGraphDetails(graph1_info.id));
|
| 38 |
+
if (graph2_info?.id) setGraph2Details(await api.graphComparison.getGraphDetails(graph2_info.id));
|
| 39 |
+
} catch (e) {
|
| 40 |
+
console.warn("ComparisonResults: failed to load graph details", e);
|
| 41 |
+
}
|
| 42 |
+
};
|
| 43 |
+
loadDetails();
|
| 44 |
+
}, [graph1_info?.id, graph2_info?.id]);
|
| 45 |
+
|
| 46 |
+
const entityExamples = useMemo(() => {
|
| 47 |
+
const createKey = (e: any) => `${(e?.type || "").toLowerCase()} ${(e?.name || "").toLowerCase()}`.trim();
|
| 48 |
+
const toLabel = (e: any) => `${e?.name || "(unnamed)"}${e?.type ? ` (${e.type})` : ""}`;
|
| 49 |
+
const e1 = graph1Details?.entities || [];
|
| 50 |
+
const e2 = graph2Details?.entities || [];
|
| 51 |
+
const set1 = new Map<string, any>();
|
| 52 |
+
const set2 = new Map<string, any>();
|
| 53 |
+
e1.forEach((x) => set1.set(createKey(x), x));
|
| 54 |
+
e2.forEach((x) => set2.set(createKey(x), x));
|
| 55 |
+
const overlap: string[] = [];
|
| 56 |
+
const unique1: string[] = [];
|
| 57 |
+
const unique2: string[] = [];
|
| 58 |
+
for (const [k, v] of set1) { if (k && set2.has(k)) overlap.push(toLabel(v)); else unique1.push(toLabel(v)); }
|
| 59 |
+
for (const [k, v] of set2) { if (!k) continue; if (!set1.has(k)) unique2.push(toLabel(v)); }
|
| 60 |
+
return { overlap: overlap.slice(0, 8), unique1: unique1.slice(0, 8), unique2: unique2.slice(0, 8) };
|
| 61 |
+
}, [graph1Details, graph2Details]);
|
| 62 |
+
|
| 63 |
+
const relationExamples = useMemo(() => {
|
| 64 |
+
const createKey = (r: any) => `${(r?.type || "").toLowerCase()} ${(r?.description || "").toLowerCase()}`.trim();
|
| 65 |
+
const toLabel = (r: any) => `${r?.type || "RELATION"}${r?.description ? ` — ${r.description}` : ""}`;
|
| 66 |
+
const r1 = graph1Details?.relations || [];
|
| 67 |
+
const r2 = graph2Details?.relations || [];
|
| 68 |
+
const set1 = new Map<string, any>();
|
| 69 |
+
const set2 = new Map<string, any>();
|
| 70 |
+
r1.forEach((x) => set1.set(createKey(x), x));
|
| 71 |
+
r2.forEach((x) => set2.set(createKey(x), x));
|
| 72 |
+
const overlap: string[] = [];
|
| 73 |
+
const unique1: string[] = [];
|
| 74 |
+
const unique2: string[] = [];
|
| 75 |
+
for (const [k, v] of set1) { if (k && set2.has(k)) overlap.push(toLabel(v)); else unique1.push(toLabel(v)); }
|
| 76 |
+
for (const [k, v] of set2) { if (!k) continue; if (!set1.has(k)) unique2.push(toLabel(v)); }
|
| 77 |
+
return { overlap: overlap.slice(0, 8), unique1: unique1.slice(0, 8), unique2: unique2.slice(0, 8) };
|
| 78 |
+
}, [graph1Details, graph2Details]);
|
| 79 |
+
|
| 80 |
// Get entity and relation counts from the actual metrics
|
| 81 |
const graph1_entity_count =
|
| 82 |
results.entity_metrics.unique_to_graph1 +
|
|
|
|
| 143 |
|
| 144 |
return (
|
| 145 |
<div className="space-y-6">
|
| 146 |
+
{/* Key Insights */}
|
| 147 |
+
<Card>
|
| 148 |
+
<CardHeader>
|
| 149 |
+
<CardTitle className="flex items-center gap-2">
|
| 150 |
+
<Lightbulb className="h-5 w-5" />
|
| 151 |
+
Key Insights
|
| 152 |
+
</CardTitle>
|
| 153 |
+
</CardHeader>
|
| 154 |
+
<CardContent>
|
| 155 |
+
<ul className="text-sm space-y-2 list-disc pl-4">
|
| 156 |
+
<li>
|
| 157 |
+
Entities overlap {formatPercentage(results.entity_metrics.overlap_ratio)} • shared {results.entity_metrics.overlap_count}; unique G1 {results.entity_metrics.unique_to_graph1}, G2 {results.entity_metrics.unique_to_graph2}.
|
| 158 |
+
</li>
|
| 159 |
+
<li>
|
| 160 |
+
Relations overlap {formatPercentage(results.relation_metrics.overlap_ratio)} • shared {results.relation_metrics.overlap_count}; unique G1 {results.relation_metrics.unique_to_graph1}, G2 {results.relation_metrics.unique_to_graph2}.
|
| 161 |
+
</li>
|
| 162 |
+
<li>
|
| 163 |
+
Structural density difference {formatNumber(Math.abs(results.structural_metrics.density_difference), 3)} • common patterns {results.structural_metrics.common_patterns_count}.
|
| 164 |
+
</li>
|
| 165 |
+
</ul>
|
| 166 |
+
</CardContent>
|
| 167 |
+
</Card>
|
| 168 |
+
|
| 169 |
{/* Header Summary */}
|
| 170 |
<Card>
|
| 171 |
<CardHeader>
|
|
|
|
| 335 |
className="h-2"
|
| 336 |
/>
|
| 337 |
</div>
|
| 338 |
+
{/* Examples */}
|
| 339 |
+
{(entityExamples.overlap.length || entityExamples.unique1.length || entityExamples.unique2.length) ? (
|
| 340 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
| 341 |
+
<div>
|
| 342 |
+
<div className="text-xs font-semibold mb-2">Common examples</div>
|
| 343 |
+
<div className="flex flex-wrap gap-1">
|
| 344 |
+
{entityExamples.overlap.map((e, i) => (
|
| 345 |
+
<Badge key={`e-ov-${i}`} variant="secondary">{e}</Badge>
|
| 346 |
+
))}
|
| 347 |
+
{!entityExamples.overlap.length && <span className="text-xs text-muted-foreground">None</span>}
|
| 348 |
+
</div>
|
| 349 |
+
</div>
|
| 350 |
+
<div>
|
| 351 |
+
<div className="text-xs font-semibold mb-2">Unique to Graph 1</div>
|
| 352 |
+
<div className="flex flex-wrap gap-1">
|
| 353 |
+
{entityExamples.unique1.map((e, i) => (
|
| 354 |
+
<Badge key={`e-u1-${i}`} variant="outline">{e}</Badge>
|
| 355 |
+
))}
|
| 356 |
+
{!entityExamples.unique1.length && <span className="text-xs text-muted-foreground">None</span>}
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
<div>
|
| 360 |
+
<div className="text-xs font-semibold mb-2">Unique to Graph 2</div>
|
| 361 |
+
<div className="flex flex-wrap gap-1">
|
| 362 |
+
{entityExamples.unique2.map((e, i) => (
|
| 363 |
+
<Badge key={`e-u2-${i}`} variant="outline">{e}</Badge>
|
| 364 |
+
))}
|
| 365 |
+
{!entityExamples.unique2.length && <span className="text-xs text-muted-foreground">None</span>}
|
| 366 |
+
</div>
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
) : null}
|
| 370 |
</div>
|
| 371 |
</AccordionContent>
|
| 372 |
</AccordionItem>
|
|
|
|
| 449 |
className="h-2"
|
| 450 |
/>
|
| 451 |
</div>
|
| 452 |
+
{/* Examples */}
|
| 453 |
+
{(relationExamples.overlap.length || relationExamples.unique1.length || relationExamples.unique2.length) ? (
|
| 454 |
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
| 455 |
+
<div>
|
| 456 |
+
<div className="text-xs font-semibold mb-2">Common examples</div>
|
| 457 |
+
<div className="flex flex-wrap gap-1">
|
| 458 |
+
{relationExamples.overlap.map((e, i) => (
|
| 459 |
+
<Badge key={`r-ov-${i}`} variant="secondary">{e}</Badge>
|
| 460 |
+
))}
|
| 461 |
+
{!relationExamples.overlap.length && <span className="text-xs text-muted-foreground">None</span>}
|
| 462 |
+
</div>
|
| 463 |
+
</div>
|
| 464 |
+
<div>
|
| 465 |
+
<div className="text-xs font-semibold mb-2">Unique to Graph 1</div>
|
| 466 |
+
<div className="flex flex-wrap gap-1">
|
| 467 |
+
{relationExamples.unique1.map((e, i) => (
|
| 468 |
+
<Badge key={`r-u1-${i}`} variant="outline">{e}</Badge>
|
| 469 |
+
))}
|
| 470 |
+
{!relationExamples.unique1.length && <span className="text-xs text-muted-foreground">None</span>}
|
| 471 |
+
</div>
|
| 472 |
+
</div>
|
| 473 |
+
<div>
|
| 474 |
+
<div className="text-xs font-semibold mb-2">Unique to Graph 2</div>
|
| 475 |
+
<div className="flex flex-wrap gap-1">
|
| 476 |
+
{relationExamples.unique2.map((e, i) => (
|
| 477 |
+
<Badge key={`r-u2-${i}`} variant="outline">{e}</Badge>
|
| 478 |
+
))}
|
| 479 |
+
{!relationExamples.unique2.length && <span className="text-xs text-muted-foreground">None</span>}
|
| 480 |
+
</div>
|
| 481 |
+
</div>
|
| 482 |
+
</div>
|
| 483 |
+
) : null}
|
| 484 |
</div>
|
| 485 |
</AccordionContent>
|
| 486 |
</AccordionItem>
|