wu981526092 commited on
Commit
9c5dc16
·
1 Parent(s): b9fa64b
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>