File size: 21,997 Bytes
c2ea5ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
import React, { useState, useEffect, useRef, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import {
  ArrowLeft,
  ZoomIn,
  ZoomOut,
  RotateCcw,
  AlertCircle,
} from "lucide-react";
// import { useAgentGraph } from "@/context/AgentGraphContext"; // Not used in this component
import {
  TemporalGraphData,
  TemporalControlState,
  TemporalNode,
  TemporalLink,
} from "@/types/temporal";
import {
  CytoscapeGraphCore,
  ElementSelectionCallbacks,
} from "@/lib/cytoscape-graph-core";
import { TemporalDataAdapter } from "@/lib/graph-data-adapters";
import { createTemporalGraphConfig } from "@/lib/graph-config-factory";
import { TemporalControls } from "../temporal/TemporalControls";
import { ElementInfoSidebar } from "../temporal/ElementInfoSidebar";

interface TemporalGraphVisualizerProps {
  temporalData: TemporalGraphData;
  onBack?: () => void;
}

export const TemporalGraphVisualizer: React.FC<
  TemporalGraphVisualizerProps
> = ({ temporalData, onBack }) => {
  // const { actions } = useAgentGraph();
  // SVG ref removed - using container for Cytoscape
  const containerRef = useRef<HTMLDivElement>(null);
  const cytoscapeRef = useRef<CytoscapeGraphCore | null>(null);
  const animationIntervalRef = useRef<NodeJS.Timeout | null>(null);
  const dataAdapter = useRef(new TemporalDataAdapter());
  const isInitializedRef = useRef(false);
  const isDestroyingRef = useRef(false);

  // State for temporal controls
  const [controlState, setControlState] = useState<TemporalControlState>({
    isPlaying: false,
    currentWindowIndex: 0,
    animationSpeed: 1.0,
    showingFullVersion: false,
    totalWindows: temporalData.windows.length,
  });

  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [graphStats, setGraphStats] = useState<{
    nodes: number;
    links: number;
    label: string;
  }>({ nodes: 0, links: 0, label: "Loading..." });

  // Selection state
  const [selectedElement, setSelectedElement] = useState<
    TemporalNode | TemporalLink | null
  >(null);
  const [selectedElementType, setSelectedElementType] = useState<
    "node" | "link" | null
  >(null);

  // Load specific window data
  const loadWindow = useCallback(
    (windowIndex: number) => {
      console.log(
        `TemporalGraphVisualizer: loadWindow called with index ${windowIndex}`
      );

      if (
        !cytoscapeRef.current ||
        windowIndex < 0 ||
        windowIndex >= temporalData.windows.length
      ) {
        console.warn(
          `TemporalGraphVisualizer: Invalid window index ${windowIndex} or missing Cytoscape core`
        );
        return;
      }

      try {
        const window = temporalData.windows[windowIndex];
        console.log(
          `TemporalGraphVisualizer: Loading window ${windowIndex}:`,
          window
        );

        if (!window || (!window.entities && !window.relations)) {
          console.warn(
            `TemporalGraphVisualizer: Window ${windowIndex} has no entities or relations`
          );
          setError(`Window ${windowIndex + 1} contains no graph data`);
          return;
        }

        const adaptedData = dataAdapter.current.adapt({
          nodes: window.entities,
          links: window.relations,
          metadata: window.metadata,
        });
        cytoscapeRef.current.updateGraph(adaptedData, true);

        setGraphStats({
          nodes: window?.entities?.length || 0,
          links: window?.relations?.length || 0,
          label: `Window ${windowIndex + 1}`,
        });

        setControlState((prev) => ({
          ...prev,
          currentWindowIndex: windowIndex,
          showingFullVersion: false,
        }));

        console.log(
          `TemporalGraphVisualizer: Successfully loaded window ${windowIndex}`
        );
      } catch (err) {
        console.error(
          `TemporalGraphVisualizer: Error loading window ${windowIndex}:`,
          err
        );
        setError(
          `Failed to load window ${windowIndex + 1}: ${
            err instanceof Error ? err.message : String(err)
          }`
        );
      }
    },
    [temporalData]
  );

  // Initialize Cytoscape visualization
  useEffect(() => {
    console.log("TemporalGraphVisualizer: useEffect triggered", {
      containerRef: !!containerRef.current,
      temporalData: temporalData,
    });

    // Reset initialization state when data changes
    isInitializedRef.current = false;
    isDestroyingRef.current = false;

    // Add a small delay to ensure DOM is fully rendered
    const initializeVisualization = async () => {
      try {
        if (
          !containerRef.current ||
          isInitializedRef.current ||
          isDestroyingRef.current
        ) {
          console.log(
            "TemporalGraphVisualizer: Refs not ready or already initialized",
            {
              containerRef: !!containerRef.current,
              isInitialized: isInitializedRef.current,
              isDestroying: isDestroyingRef.current,
            }
          );
          return;
        }

        isInitializedRef.current = true;

        // Wait a bit more for layout to settle
        await new Promise((resolve) => setTimeout(resolve, 200));

        const container = containerRef.current;
        let width = container.clientWidth || 800; // Fallback width
        let height = container.clientHeight || 600; // Fallback height

        // For full-screen temporal visualization, use viewport dimensions if container is small
        if (width < 800 || height < 600) {
          const viewportWidth = window.innerWidth;
          const viewportHeight = window.innerHeight;

          // Account for header, controls, padding, and sidebar
          const headerHeight = 80; // Approximate header height
          const controlsHeight = 60; // Approximate controls height
          const padding = 32; // Total padding
          const sidebarWidth = 384; // Approximate sidebar width (w-96 = 384px)

          width = Math.max(width, viewportWidth - sidebarWidth - 50); // Account for sidebar and margin
          height = Math.max(
            height,
            viewportHeight - headerHeight - controlsHeight - padding
          );

          console.log(
            "TemporalGraphVisualizer: Using viewport-based dimensions",
            {
              viewportWidth,
              viewportHeight,
              sidebarWidth,
              calculatedWidth: width,
              calculatedHeight: height,
            }
          );
        }

        console.log("TemporalGraphVisualizer: Container dimensions", {
          width,
          height,
          clientWidth: container.clientWidth,
          clientHeight: container.clientHeight,
          offsetWidth: container.offsetWidth,
          offsetHeight: container.offsetHeight,
          scrollWidth: container.scrollWidth,
          scrollHeight: container.scrollHeight,
        });

        // If container has zero dimensions, try to get parent dimensions
        let finalWidth = width;
        let finalHeight = height;

        if (width === 0 || height === 0) {
          const parent = container.parentElement;
          if (parent) {
            finalWidth = parent.clientWidth || 800;
            finalHeight = parent.clientHeight || 600;
            console.log("TemporalGraphVisualizer: Using parent dimensions", {
              parentWidth: finalWidth,
              parentHeight: finalHeight,
            });
          }
        }

        if (finalWidth === 0 || finalHeight === 0) {
          console.warn(
            "TemporalGraphVisualizer: Container has zero dimensions, using fallbacks"
          );
          finalWidth = Math.max(finalWidth, 800);
          finalHeight = Math.max(finalHeight, 500); // Ensure minimum height
        }

        // Ensure minimum dimensions for usability
        finalWidth = Math.max(finalWidth, 400);
        finalHeight = Math.max(finalHeight, 400);

        const config = createTemporalGraphConfig({
          width: finalWidth,
          height: finalHeight,
        });

        console.log(
          "TemporalGraphVisualizer: Creating CytoscapeGraphCore with config",
          config
        );

        // Create selection callbacks
        const selectionCallbacks: ElementSelectionCallbacks = {
          onNodeSelect: (node: TemporalNode) => {
            setSelectedElement(node);
            setSelectedElementType("node");
          },
          onLinkSelect: (link: TemporalLink) => {
            setSelectedElement(link);
            setSelectedElementType("link");
          },
          onClearSelection: () => {
            setSelectedElement(null);
            setSelectedElementType(null);
          },
        };

        // Check if container is already being used by Cytoscape
        if (containerRef.current.hasAttribute("data-cytoscape-active")) {
          console.warn(
            "Container already has active Cytoscape instance, skipping initialization"
          );
          return;
        }

        // Mark container as active
        containerRef.current.setAttribute("data-cytoscape-active", "true");

        cytoscapeRef.current = new CytoscapeGraphCore(
          containerRef.current,
          config,
          selectionCallbacks
        );

        // Load initial window
        if (temporalData.windows.length > 0) {
          console.log(
            "TemporalGraphVisualizer: Loading initial window",
            temporalData.windows[0]
          );
          loadWindow(0);
        } else {
          console.warn(
            "TemporalGraphVisualizer: No windows available in temporal data"
          );
          setError("No temporal windows available for visualization");
        }

        console.log(
          "TemporalGraphVisualizer: Initialization complete, setting loading to false"
        );
        setLoading(false);
      } catch (err) {
        console.error(
          "TemporalGraphVisualizer: Error initializing Cytoscape visualization:",
          err
        );
        setError(
          `Failed to initialize visualization: ${
            err instanceof Error ? err.message : String(err)
          }`
        );
        setLoading(false);
      }
    };

    // Function to check if refs are ready and initialize
    const checkAndInitialize = () => {
      if (!containerRef.current) {
        console.log("TemporalGraphVisualizer: Missing refs, retrying...");
        return false;
      }
      return true;
    };

    // If refs not ready immediately, set up a retry mechanism
    if (!checkAndInitialize()) {
      let retryCount = 0;
      const maxRetries = 20;

      const retryInterval = setInterval(() => {
        retryCount++;
        console.log(
          `TemporalGraphVisualizer: Retry attempt ${retryCount}/${maxRetries}`
        );

        if (checkAndInitialize()) {
          clearInterval(retryInterval);
          // Continue with initialization
          initializeVisualization();
        } else if (retryCount >= maxRetries) {
          clearInterval(retryInterval);
          console.error(
            "TemporalGraphVisualizer: Max retries reached, giving up"
          );
          setError(
            "Failed to initialize visualization: DOM elements not ready"
          );
          setLoading(false);
        }
      }, 200);

      return () => {
        clearInterval(retryInterval);
        isDestroyingRef.current = true;
        if (cytoscapeRef.current) {
          cytoscapeRef.current.destroy();
          cytoscapeRef.current = null;
        }
        if (animationIntervalRef.current) {
          clearInterval(animationIntervalRef.current);
          animationIntervalRef.current = null;
        }
        isInitializedRef.current = false;
      };
    }

    // If refs are ready, initialize immediately
    const timeoutId = setTimeout(initializeVisualization, 100);

    // Cleanup
    return () => {
      clearTimeout(timeoutId);
      isDestroyingRef.current = true;
      if (cytoscapeRef.current) {
        cytoscapeRef.current.destroy();
        cytoscapeRef.current = null;
      }
      if (animationIntervalRef.current) {
        clearInterval(animationIntervalRef.current);
        animationIntervalRef.current = null;
      }
      isInitializedRef.current = false;
    };
  }, [temporalData, loadWindow]);

  // Handle window resize for full-screen mode
  useEffect(() => {
    const handleResize = () => {
      if (cytoscapeRef.current && containerRef.current) {
        const container = containerRef.current;
        let width = container.clientWidth;
        let height = container.clientHeight;

        // Use viewport dimensions for better full-screen experience
        if (width < 800 || height < 600) {
          const viewportWidth = window.innerWidth;
          const viewportHeight = window.innerHeight;

          const headerHeight = 80;
          const controlsHeight = 60;
          const padding = 32;
          const sidebarWidth = 384; // Account for sidebar

          width = Math.max(width, viewportWidth - sidebarWidth - 50);
          height = Math.max(
            height,
            viewportHeight - headerHeight - controlsHeight - padding
          );
        }

        console.log("TemporalGraphVisualizer: Resizing to", { width, height });
        cytoscapeRef.current.resize(width, height);
      }
    };

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  // Cleanup effect for component unmount
  useEffect(() => {
    return () => {
      // Final cleanup when component unmounts
      isDestroyingRef.current = true;
      if (cytoscapeRef.current) {
        cytoscapeRef.current.destroy();
        cytoscapeRef.current = null;
      }
      if (animationIntervalRef.current) {
        clearInterval(animationIntervalRef.current);
        animationIntervalRef.current = null;
      }
      isInitializedRef.current = false;
    };
  }, []);

  // Load full version
  const loadFullVersion = useCallback(() => {
    if (!cytoscapeRef.current || !temporalData.full_kg) {
      return;
    }

    try {
      console.log("Loading full version KG:", temporalData.full_kg);

      const adaptedData = dataAdapter.current.adapt({
        nodes: temporalData.full_kg.entities,
        links: temporalData.full_kg.relations,
        metadata: temporalData.full_kg.metadata,
      });
      cytoscapeRef.current.updateGraph(adaptedData, true);

      setGraphStats({
        nodes: temporalData.full_kg.entities?.length || 0,
        links: temporalData.full_kg.relations?.length || 0,
        label: "Full Version",
      });

      setControlState((prev) => ({
        ...prev,
        showingFullVersion: true,
      }));
    } catch (err) {
      console.error("Error loading full version:", err);
      setError("Failed to load full version");
    }
  }, [temporalData]);

  // Animation control functions
  const startAnimation = useCallback(() => {
    if (controlState.isPlaying || temporalData.windows.length < 2) {
      return;
    }

    setControlState((prev) => ({
      ...prev,
      isPlaying: true,
      currentWindowIndex: 0,
    }));
    loadWindow(0);

    animationIntervalRef.current = setInterval(() => {
      setControlState((prev) => {
        const nextIndex = prev.currentWindowIndex + 1;
        if (nextIndex >= temporalData.windows.length) {
          // Animation complete
          if (animationIntervalRef.current) {
            clearInterval(animationIntervalRef.current);
          }
          return { ...prev, isPlaying: false };
        } else {
          loadWindow(nextIndex);
          return { ...prev, currentWindowIndex: nextIndex };
        }
      });
    }, 2000 / controlState.animationSpeed);
  }, [
    controlState.isPlaying,
    controlState.animationSpeed,
    temporalData.windows.length,
    loadWindow,
  ]);

  const stopAnimation = useCallback(() => {
    if (animationIntervalRef.current) {
      clearInterval(animationIntervalRef.current);
      animationIntervalRef.current = null;
    }
    setControlState((prev) => ({ ...prev, isPlaying: false }));
  }, []);

  const handlePlay = () => startAnimation();
  const handlePause = () => stopAnimation();

  const handlePrevious = () => {
    if (controlState.currentWindowIndex > 0) {
      loadWindow(controlState.currentWindowIndex - 1);
    }
  };

  const handleNext = () => {
    if (controlState.currentWindowIndex < temporalData.windows.length - 1) {
      loadWindow(controlState.currentWindowIndex + 1);
    }
  };

  const handleWindowChange = (windowIndex: number) => {
    loadWindow(windowIndex);
  };

  const handleSpeedChange = (speed: number) => {
    setControlState((prev) => ({ ...prev, animationSpeed: speed }));
  };

  const handleToggleFullVersion = () => {
    if (controlState.showingFullVersion) {
      loadWindow(controlState.currentWindowIndex);
    } else {
      loadFullVersion();
    }
  };

  const handleReset = () => {
    stopAnimation();
    if (cytoscapeRef.current) {
      cytoscapeRef.current.resetZoom();
    }
    loadWindow(0);
  };

  const handleZoomIn = () => {
    if (cytoscapeRef.current) {
      cytoscapeRef.current.zoomIn();
    }
  };

  const handleZoomOut = () => {
    if (cytoscapeRef.current) {
      cytoscapeRef.current.zoomOut();
    }
  };

  const handleResetZoom = () => {
    if (cytoscapeRef.current) {
      cytoscapeRef.current.resetZoom();
    }
  };

  if (error) {
    return (
      <div className="flex-1 flex items-center justify-center">
        <Card className="w-full max-w-md">
          <CardContent className="pt-6">
            <div className="text-center">
              <AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
              <h3 className="text-lg font-semibold mb-2">
                Visualization Error
              </h3>
              <p className="text-muted-foreground mb-4">{error}</p>
              <Button onClick={() => window.location.reload()}>
                Try Again
              </Button>
            </div>
          </CardContent>
        </Card>
      </div>
    );
  }

  return (
    <div className="w-full h-full flex flex-col">
      {/* Header */}
      <div className="flex items-center justify-between px-6 py-4 border-b bg-background/50">
        <div className="flex items-center gap-4">
          <Button variant="ghost" size="sm" onClick={onBack}>
            <ArrowLeft className="h-4 w-4 mr-2" />
            Back
          </Button>
          <div>
            <h1 className="text-xl font-semibold">
              {temporalData.trace_title}
            </h1>
            <p className="text-sm text-muted-foreground">
              Temporal Knowledge Graph Visualization
            </p>
          </div>
        </div>
        <div className="flex items-center gap-2">
          <Badge variant="outline">
            {graphStats.nodes} nodes, {graphStats.links} relations
          </Badge>
          <Badge variant="secondary">{graphStats.label}</Badge>
        </div>
      </div>

      {/* Controls */}
      <div className="px-6 py-3 border-b bg-background/30">
        <TemporalControls
          state={controlState}
          onPlay={handlePlay}
          onPause={handlePause}
          onPrevious={handlePrevious}
          onNext={handleNext}
          onWindowChange={handleWindowChange}
          onSpeedChange={handleSpeedChange}
          onToggleFullVersion={handleToggleFullVersion}
          onReset={handleReset}
          hasFullVersion={temporalData.has_full_version}
          disabled={loading}
        />
      </div>

      {/* Visualization */}
      <div className="flex-1 flex min-h-0">
        <div className="flex-1 relative p-4">
          <div
            ref={containerRef}
            key={`temporal-graph-${temporalData.trace_id || "default"}`}
            className="w-full h-full bg-background border rounded-lg relative overflow-hidden shadow-sm"
          >
            {/* Cytoscape will render here - no SVG needed */}

            {/* Loading Overlay */}
            {loading && (
              <div className="absolute inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center">
                <div className="text-center">
                  <div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin mx-auto mb-4" />
                  <p className="text-muted-foreground">
                    Loading temporal visualization...
                  </p>
                </div>
              </div>
            )}

            {/* Zoom Controls */}
            <div className="absolute top-4 right-4 flex flex-col gap-2">
              <Button
                variant="outline"
                size="sm"
                onClick={handleZoomIn}
                disabled={loading}
              >
                <ZoomIn className="h-4 w-4" />
              </Button>
              <Button
                variant="outline"
                size="sm"
                onClick={handleZoomOut}
                disabled={loading}
              >
                <ZoomOut className="h-4 w-4" />
              </Button>
              <Button
                variant="outline"
                size="sm"
                onClick={handleResetZoom}
                disabled={loading}
              >
                <RotateCcw className="h-4 w-4" />
              </Button>
            </div>
          </div>
        </div>

        {/* Element Info Sidebar */}
        <ElementInfoSidebar
          selectedElement={selectedElement}
          selectedElementType={selectedElementType}
          currentData={
            cytoscapeRef.current?.getCurrentData() || { nodes: [], links: [] }
          }
        />
      </div>
    </div>
  );
};