From 7814417e9caa2f02c6e40434382f0a99387b47be Mon Sep 17 00:00:00 2001 From: George Pearse Date: Sun, 23 Nov 2025 18:34:28 -0500 Subject: [PATCH 1/2] Add Rust optimization examples from squeeze library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds two new Genesis optimization examples targeting the squeeze high-performance Rust library (https://github.com/GeorgePearse/squeeze): ## HNSW (Hierarchical Navigable Small World) Search - Target: `search_layer` beam search algorithm - Metric: recall@10 × speed_factor × 100 - Initial experiments show +14% improvement (53.79 → 61.32) - Key optimization: Vec visited tracking vs HashSet ## t-SNE Gradient Computation - Target: O(n²) gradient computation called 1000× per embedding - Metric: trustworthiness × speed_factor × 100 - Initial experiments show +3.7% improvement (149.50 → 155.08) - Key optimization: blocked loop unrolling with symmetry exploitation ## Files Added - `examples/squeeze_hnsw/` - Standalone HNSW implementation - `examples/squeeze_tsne/` - Standalone t-SNE gradient implementation - `configs/task/squeeze_*.yaml` - Task configurations - `configs/variant/squeeze_*.yaml` - Variant configurations (local + E2B) - `ROADMAP.md` - Comprehensive language support roadmap 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 2 +- ROADMAP.md | 135 ++++++ configs/task/squeeze_hnsw.yaml | 55 +++ configs/task/squeeze_tsne.yaml | 63 +++ configs/variant/squeeze_hnsw_e2b.yaml | 39 ++ configs/variant/squeeze_hnsw_example.yaml | 11 + configs/variant/squeeze_tsne_example.yaml | 20 + examples/squeeze_hnsw/evaluate.py | 250 +++++++++++ examples/squeeze_hnsw/initial.rs | 431 ++++++++++++++++++ examples/squeeze_tsne/evaluate.py | 206 +++++++++ examples/squeeze_tsne/initial.rs | 509 ++++++++++++++++++++++ 11 files changed, 1720 insertions(+), 1 deletion(-) create mode 100644 ROADMAP.md create mode 100644 configs/task/squeeze_hnsw.yaml create mode 100644 configs/task/squeeze_tsne.yaml create mode 100644 configs/variant/squeeze_hnsw_e2b.yaml create mode 100644 configs/variant/squeeze_hnsw_example.yaml create mode 100644 configs/variant/squeeze_tsne_example.yaml create mode 100644 examples/squeeze_hnsw/evaluate.py create mode 100644 examples/squeeze_hnsw/initial.rs create mode 100644 examples/squeeze_tsne/evaluate.py create mode 100644 examples/squeeze_tsne/initial.rs diff --git a/README.md b/README.md index 3aecf7a..4baddd7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -Integrating e2b (or similar), and modal (for GPU code) would be a meaningful upgrade, so that you could further parallelize execution, do need to check how the existing other agents work though. Might already do similar

@@ -31,6 +30,7 @@ The framework supports **parallel evaluation of candidates** locally, on a Slurm | 📓 **[Tutorial Notebook](examples/genesis_tutorial.ipynb)** | Interactive walkthrough of Genesis features | Hands-on examples, configuration, best practices | | ⚙️ **[Configuration](docs/configuration.md)** | Comprehensive configuration reference | All config options, optimization settings, advanced features | | 🎨 **[WebUI](docs/webui.md)** | Interactive visualization and monitoring | Real-time tracking, result analysis, debugging tools | +| 🗺️ **[Roadmap](ROADMAP.md)** | Future plans and language support | Supported languages, execution backends, planned features | ## Installation & Quick Start 🚀 diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..ae732a1 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,135 @@ +# Genesis Roadmap + +## Vision + +Genesis aims to be the universal framework for LLM-driven code evolution across any programming language, execution environment, and optimization objective. + +--- + +## Current Language Support + +| Language | Local | Slurm | E2B (base) | E2B (custom) | Notes | +|----------|:-----:|:-----:|:----------:|:------------:|-------| +| **Python** | ✅ | ✅ | ✅ | ✅ | First-class support, all features | +| **Rust** | ✅ | ✅ | ❌ | ✅ | Needs `rustc` in environment | +| **C++** | ✅ | ✅ | ❌ | ✅ | Needs `g++` or `clang++` | +| **CUDA** | ⚠️ | ✅ | ❌ | ❌ | Requires GPU + `nvcc` | +| **JavaScript/TypeScript** | ✅ | ✅ | ✅ | ✅ | Node.js available in E2B base | + +### Adding New Language Support + +To evolve code in any language, you need: + +1. **Compiler/Interpreter** in the execution environment +2. **Python evaluation wrapper** that: + - Compiles the evolved code (if needed) + - Runs it against test cases + - Returns a numeric fitness score + +See `examples/mask_to_seg_rust/` for a complete Rust example. + +--- + +## Execution Backend Status + +| Backend | Status | Parallelism | GPU Support | Best For | +|---------|:------:|:-----------:|:-----------:|----------| +| **Local** | ✅ Done | 1-4 jobs | If available | Development, testing | +| **Slurm (Docker)** | ✅ Done | Unlimited | ✅ Yes | HPC clusters | +| **Slurm (Conda)** | ✅ Done | Unlimited | ✅ Yes | HPC clusters | +| **E2B** | ✅ Done | ~50 jobs | ❌ No | Cloud parallel execution | +| **Modal** | 🔜 Planned | Unlimited | ✅ Yes | Serverless GPU | +| **Ray** | 💭 Idea | Unlimited | ✅ Yes | Distributed clusters | + +--- + +## Planned Improvements + +### High Priority + +#### E2B Templates for Compiled Languages +- [ ] Pre-built E2B templates with common toolchains: + - `genesis-rust` - Rust toolchain (rustc, cargo) + - `genesis-cpp` - C++ toolchain (g++, clang++, cmake) + - `genesis-go` - Go toolchain + - `genesis-full` - All languages combined +- [ ] One-command template deployment +- [ ] Documentation for custom template creation + +#### Modal Integration for GPU Code +- [ ] CUDA kernel evolution with GPU execution +- [ ] PyTorch/JAX model optimization +- [ ] Serverless GPU with automatic scaling +- [ ] Cost tracking and budget limits + +#### Improved Rust Support +- [ ] Cargo project support (not just single-file rustc) +- [ ] Crate dependency management +- [ ] SIMD-aware optimization prompts +- [ ] Benchmark-driven fitness (criterion.rs integration) + +### Medium Priority + +#### Language-Specific LLM Optimization Hints +- [ ] **Rust**: Ownership/borrowing patterns, SIMD intrinsics, zero-copy +- [ ] **C++**: Template metaprogramming, cache optimization, vectorization +- [ ] **CUDA**: Warp efficiency, shared memory, occupancy optimization +- [ ] **Python**: NumPy vectorization, Cython hints, memory views + +#### Multi-Language Project Evolution +- [ ] Python + Rust (PyO3 bindings) +- [ ] Python + C++ (pybind11) +- [ ] Evolve both sides of FFI boundaries +- [ ] Cross-language fitness evaluation + +#### Enhanced Parallelism +- [ ] Adaptive `max_parallel_jobs` based on backend capacity +- [ ] Job priority queuing +- [ ] Preemption for higher-fitness candidates +- [ ] Distributed island model across backends + +### Future Exploration + +#### Additional Languages +| Language | Use Case | Complexity | +|----------|----------|------------| +| **WebAssembly** | Browser-based evolution, portable binaries | Medium | +| **Go** | Systems programming, microservices | Easy | +| **Julia** | Scientific computing, differentiable programming | Medium | +| **Zig** | Systems programming with safety | Medium | +| **Mojo** | Python syntax + systems performance | Hard (new language) | +| **Haskell** | Functional algorithm optimization | Medium | + +#### Advanced Features +- [ ] **Auto-vectorization verification** - Ensure SIMD is actually used +- [ ] **Formal verification integration** - Prove evolved code correctness +- [ ] **Energy-aware optimization** - Minimize power consumption +- [ ] **Hardware-specific tuning** - ARM vs x86, specific CPU features + +--- + +## Completed Milestones + +- [x] Core evolution framework with island model +- [x] Local execution backend +- [x] Slurm cluster support (Docker + Conda) +- [x] E2B cloud sandbox integration +- [x] Python language support +- [x] Rust language support (single-file) +- [x] WebUI for experiment monitoring +- [x] Novelty search and diversity maintenance +- [x] Multi-LLM support (OpenAI, Anthropic, Google, DeepSeek) +- [x] Hydra configuration system + +--- + +## Contributing + +We welcome contributions! Priority areas: + +1. **E2B templates** for compiled languages +2. **Modal backend** implementation +3. **Language-specific examples** and evaluators +4. **Documentation** improvements + +See [AGENTS.md](AGENTS.md) for contribution guidelines. diff --git a/configs/task/squeeze_hnsw.yaml b/configs/task/squeeze_hnsw.yaml new file mode 100644 index 0000000..76b62d4 --- /dev/null +++ b/configs/task/squeeze_hnsw.yaml @@ -0,0 +1,55 @@ +exp_name: genesis_squeeze_hnsw + +evaluate_function: + _target_: examples.squeeze_hnsw.evaluate.main + program_path: ??? + results_dir: ??? + +# Local execution config +distributed_job_config: + _target_: genesis.launch.LocalJobConfig + eval_program_path: "genesis/eval_hydra.py" + +evo_config: + task_sys_msg: | + You are an expert Rust developer specializing in high-performance approximate nearest neighbor (ANN) search algorithms. + Your task is to optimize the HNSW (Hierarchical Navigable Small World) search algorithm. + + The goal is to maximize recall@10 (finding the true 10 nearest neighbors) while minimizing search time. + The combined score is: recall * speed_factor * 100 + + The current implementation is a standard HNSW beam search. Key areas to optimize: + + 1. **Data Structure Efficiency**: + - HashSet for visited nodes is O(1) but has overhead - consider alternatives + - BinaryHeap operations can be expensive - consider custom heaps or different algorithms + - Pre-allocation and capacity hints can reduce allocations + + 2. **Search Algorithm Improvements**: + - Early termination heuristics - stop searching when unlikely to find better candidates + - Better candidate selection strategies + - Adaptive ef parameter based on query characteristics + - Parallel neighbor exploration (though be careful with visited tracking) + + 3. **Distance Computation Optimization**: + - SIMD vectorization for Euclidean distance + - Early exit when distance exceeds threshold + - Cache-friendly memory access patterns + + 4. **Graph Navigation Strategies**: + - Multi-start search from multiple entry points + - Backtracking strategies when stuck in local minima + - Prefetching neighbor data + + Key constraints: + - Must compile with `rustc -O` (no external crates) + - Input: data.txt (one vector per line), queries.txt (one query per line) + - Output: JSON array of [index, distance] pairs for each query + - Must find exactly k=10 nearest neighbors per query + + Focus on the `search_layer` function marked with EVOLVE-BLOCK - this is the core beam search + that dominates execution time. + + language: rust + init_program_path: examples/squeeze_hnsw/initial.rs + job_type: local diff --git a/configs/task/squeeze_tsne.yaml b/configs/task/squeeze_tsne.yaml new file mode 100644 index 0000000..e685ee8 --- /dev/null +++ b/configs/task/squeeze_tsne.yaml @@ -0,0 +1,63 @@ +# Task configuration for t-SNE gradient optimization +# Target: compute_gradient function in squeeze's t-SNE implementation + +exp_name: genesis_squeeze_tsne + +evaluate_function: + _target_: examples.squeeze_tsne.evaluate.main + program_path: ??? + results_dir: ??? + +# Local execution config +distributed_job_config: + _target_: genesis.launch.LocalJobConfig + eval_program_path: "genesis/eval_hydra.py" + +evo_config: + task_sys_msg: | + You are an expert Rust developer specializing in high-performance numerical computing. + Your task is to optimize the t-SNE gradient computation, which is O(n^2) and called + 1000+ times per embedding (once per iteration). + + The goal is to maximize trustworthiness (local neighborhood preservation) while minimizing + computation time. Combined score = trustworthiness * speed_factor * 100 + + The gradient formula is: + grad_i = 4 * sum_j[(p_ij - q_ij) * (1 + ||y_i - y_j||^2)^-1 * (y_i - y_j)] + + Key optimization opportunities: + + 1. **Loop Optimizations**: + - Unroll inner loops for n_components (typically 2) + - Use iterators instead of index-based access + - Early termination when contribution is negligible + + 2. **Memory Access Patterns**: + - Cache-friendly sequential access + - Pre-compute P - Q matrix once + - Avoid repeated bounds checking + + 3. **SIMD Vectorization**: + - Manual SIMD for distance computation (using std::arch intrinsics) + - Process multiple points in parallel + - Align data for efficient vector operations + + 4. **Algorithmic Improvements**: + - Spatial data structures (quad-tree for Barnes-Hut approximation) + - Skip negligible contributions (p_ij - q_ij ≈ 0) + - Symmetry exploitation (grad_i contribution from j = -grad_j from i) + + 5. **Parallelization**: + - Compute gradients for multiple points simultaneously + - Note: Must be deterministic for reproducibility + + Key constraints: + - Must compile with `rustc -O` (no external crates) + - Must maintain numerical stability (no NaN/Inf) + - Must preserve embedding quality (trustworthiness > 0.3) + + Focus on the functions in EVOLVE-BLOCK - these are called O(n^2 * iterations) times + and dominate execution time. + + language: rust + init_program_path: examples/squeeze_tsne/initial.rs diff --git a/configs/variant/squeeze_hnsw_e2b.yaml b/configs/variant/squeeze_hnsw_e2b.yaml new file mode 100644 index 0000000..c312d7b --- /dev/null +++ b/configs/variant/squeeze_hnsw_e2b.yaml @@ -0,0 +1,39 @@ +# E2B-accelerated HNSW optimization +# Runs evaluations in parallel E2B cloud sandboxes for ~10x speedup +# +# IMPORTANT: This task requires Rust (rustc) in the E2B sandbox. +# The default "base" template may not have Rust installed. +# Options: +# 1. Create a custom E2B template with Rust: https://e2b.dev/docs/sandbox-template +# 2. Use a community template with Rust pre-installed +# 3. Install Rust in sandbox via dependencies (slow but works) + +defaults: + - override /database@_global_: island_small + - override /evolution@_global_: small_budget + - override /task@_global_: squeeze_hnsw + - override /cluster@_global_: e2b + - _self_ + +job_config: + _target_: genesis.launch.E2BJobConfig + eval_program_path: genesis/eval_hydra.py + + # Use base template - may need custom template with Rust for this task + template: "base" + + # Longer timeout for Rust compilation + evaluation + timeout: 600 + + # Dependencies for the evaluator + dependencies: + - numpy + - opencv-python-headless + +evo_config: + job_type: "e2b" + num_generations: 100 + # Run 10 evaluations in parallel - adjust based on E2B plan limits + max_parallel_jobs: 10 + +variant_suffix: "_e2b" diff --git a/configs/variant/squeeze_hnsw_example.yaml b/configs/variant/squeeze_hnsw_example.yaml new file mode 100644 index 0000000..c0153b5 --- /dev/null +++ b/configs/variant/squeeze_hnsw_example.yaml @@ -0,0 +1,11 @@ +defaults: + - override /database@_global_: island_small + - override /evolution@_global_: small_budget + - override /task@_global_: squeeze_hnsw + - override /cluster@_global_: local + - _self_ + +evo_config: + num_generations: 50 + +variant_suffix: "_example" diff --git a/configs/variant/squeeze_tsne_example.yaml b/configs/variant/squeeze_tsne_example.yaml new file mode 100644 index 0000000..0dd70fe --- /dev/null +++ b/configs/variant/squeeze_tsne_example.yaml @@ -0,0 +1,20 @@ +# Variant configuration for t-SNE gradient optimization +# Runs locally with 50 generations + +defaults: + - override /database@_global_: island_small + - override /evolution@_global_: small_budget + - override /task@_global_: squeeze_tsne + - override /cluster@_global_: local + - _self_ + +job_config: + _target_: genesis.launch.LocalJobConfig + eval_program_path: genesis/eval_hydra.py + +evo_config: + job_type: "local" + num_generations: 50 + max_parallel_jobs: 1 + +variant_suffix: "_example" diff --git a/examples/squeeze_hnsw/evaluate.py b/examples/squeeze_hnsw/evaluate.py new file mode 100644 index 0000000..20ca557 --- /dev/null +++ b/examples/squeeze_hnsw/evaluate.py @@ -0,0 +1,250 @@ +""" +Evaluator for HNSW k-NN search optimization. + +Metrics: +- Recall@10: Fraction of true nearest neighbors found +- Speed: Queries per second +- Combined score: recall * speed_factor (higher is better) +""" + +import os +import argparse +import numpy as np +import json +import time +import subprocess +from typing import List, Dict, Any, Tuple, Optional +from genesis.core import run_genesis_eval + +# Globals for ground truth +GROUND_TRUTH = {} +BINARY_PATH = None + + +def generate_random_data(n_points: int, dim: int, seed: int) -> np.ndarray: + """Generate random dataset.""" + np.random.seed(seed) + return np.random.randn(n_points, dim).astype(np.float32) + + +def generate_queries(data: np.ndarray, n_queries: int, seed: int) -> np.ndarray: + """Generate query points (slightly perturbed from data points).""" + np.random.seed(seed + 1000) + indices = np.random.choice(len(data), n_queries, replace=False) + queries = data[indices] + np.random.randn(n_queries, data.shape[1]).astype(np.float32) * 0.1 + return queries + + +def compute_ground_truth(data: np.ndarray, queries: np.ndarray, k: int) -> List[List[int]]: + """Compute exact k-NN using brute force.""" + ground_truth = [] + for query in queries: + distances = np.sqrt(np.sum((data - query) ** 2, axis=1)) + indices = np.argsort(distances)[:k] + ground_truth.append(indices.tolist()) + return ground_truth + + +def save_data_txt(data: np.ndarray, path: str): + """Save data to text file (one vector per line, space-separated).""" + with open(path, "w") as f: + for row in data: + f.write(" ".join(f"{x:.6f}" for x in row) + "\n") + + +def compile_rust(program_path: str, output_dir: str) -> Optional[str]: + """Compile Rust program.""" + binary_path = os.path.join(output_dir, "hnsw_search") + cmd = ["rustc", "-O", program_path, "-o", binary_path] + try: + subprocess.check_call(cmd, stderr=subprocess.PIPE, timeout=60) + return binary_path + except subprocess.CalledProcessError as e: + print(f"Compilation failed: {e}") + return None + except subprocess.TimeoutExpired: + print("Compilation timed out") + return None + + +def run_search(data: np.ndarray, queries: np.ndarray, run_index: int) -> List[List[Tuple[int, float]]]: + """Run HNSW search and return results.""" + binary_path = os.environ.get("HNSW_BINARY_PATH") + if not binary_path or not os.path.exists(binary_path): + return [] + + base_dir = os.path.dirname(binary_path) + run_dir = os.path.join(base_dir, "temp", f"run_{run_index}") + os.makedirs(run_dir, exist_ok=True) + + data_path = os.path.join(run_dir, "data.txt") + query_path = os.path.join(run_dir, "queries.txt") + output_path = os.path.join(run_dir, "results.json") + + save_data_txt(data, data_path) + save_data_txt(queries, query_path) + + try: + subprocess.check_call( + [binary_path, data_path, query_path, output_path], + timeout=30, + stderr=subprocess.DEVNULL + ) + + if os.path.exists(output_path): + with open(output_path, "r") as f: + results_raw = json.load(f) + + # Parse results: [[idx, dist], [idx, dist], ...] + results = [] + for query_results in results_raw: + parsed = [(int(r[0]), float(r[1])) for r in query_results] + results.append(parsed) + return results + except Exception as e: + print(f"Run failed: {e}") + + return [] + + +def get_kwargs(run_index: int) -> Dict[str, Any]: + """Generate test case.""" + # Different sizes for robustness + configs = [ + (1000, 64, 50), # Small: 1000 points, 64 dims, 50 queries + (2000, 128, 50), # Medium: 2000 points, 128 dims + (5000, 64, 100), # Larger: 5000 points + (1000, 256, 50), # High-dim: 256 dimensions + (3000, 96, 75), # Mixed + ] + + config_idx = run_index % len(configs) + n_points, dim, n_queries = configs[config_idx] + seed = 42 + run_index * 100 + + data = generate_random_data(n_points, dim, seed) + queries = generate_queries(data, n_queries, seed) + + # Compute ground truth + k = 10 + gt = compute_ground_truth(data, queries, k) + GROUND_TRUTH[run_index] = gt + + return {"data": data, "queries": queries, "run_index": run_index} + + +def validate(result: List[List[Tuple[int, float]]]) -> Tuple[bool, Optional[str]]: + """Validate search results.""" + if not isinstance(result, list): + return False, "Result not a list" + if len(result) == 0: + return False, "Empty results" + return True, None + + +def compute_recall(results: List[List[Tuple[int, float]]], ground_truth: List[List[int]], k: int = 10) -> float: + """Compute recall@k.""" + if not results or not ground_truth: + return 0.0 + + recalls = [] + for pred, gt in zip(results, ground_truth): + pred_indices = set(r[0] for r in pred[:k]) + gt_set = set(gt[:k]) + if len(gt_set) > 0: + recall = len(pred_indices & gt_set) / len(gt_set) + recalls.append(recall) + + return np.mean(recalls) if recalls else 0.0 + + +def aggregate(results: List[Any]) -> Dict[str, Any]: + """Aggregate metrics across runs.""" + recalls = [] + + for i, result in enumerate(results): + gt = GROUND_TRUTH.get(i) + if gt is None or not result: + continue + + recall = compute_recall(result, gt, k=10) + recalls.append(recall) + + mean_recall = np.mean(recalls) if recalls else 0.0 + + return { + "mean_recall": float(mean_recall), + "num_successful_runs": len(recalls), + } + + +def main(program_path: str, results_dir: str): + """Main evaluation function.""" + global BINARY_PATH + + os.makedirs(results_dir, exist_ok=True) + temp_dir = os.path.join(results_dir, "temp") + os.makedirs(temp_dir, exist_ok=True) + + # Compile Rust program + print(f"Compiling {program_path}...") + BINARY_PATH = compile_rust(program_path, results_dir) + + if not BINARY_PATH: + print("Compilation failed, returning zero score") + metrics = { + "combined_score": 0.0, + "mean_recall": 0.0, + "correct": False, + "error": "compilation_failed" + } + with open(os.path.join(results_dir, "metrics.json"), "w") as f: + json.dump(metrics, f, indent=4) + return + + os.environ["HNSW_BINARY_PATH"] = BINARY_PATH + + # Run evaluation + metrics, correct, error = run_genesis_eval( + program_path=__file__, + results_dir=results_dir, + experiment_fn_name="run_search", + num_runs=5, + get_experiment_kwargs=get_kwargs, + validate_fn=validate, + aggregate_metrics_fn=aggregate, + ) + + if correct: + mean_time = metrics.get("execution_time_mean", 1.0) + mean_recall = metrics.get("mean_recall", 0.0) + + # Speed factor: penalize slow execution + # Target: < 1 second per run + safe_time = max(mean_time, 1e-6) + speed_factor = 1.0 / (1.0 + safe_time) + + # Combined score: recall * speed * 100 + # High recall is essential, but speed matters too + combined_score = mean_recall * speed_factor * 100.0 + + metrics["combined_score"] = combined_score + metrics["speed_factor"] = speed_factor + metrics["correct"] = True + else: + metrics["combined_score"] = 0.0 + metrics["correct"] = False + metrics["error"] = error + + with open(os.path.join(results_dir, "metrics.json"), "w") as f: + json.dump(metrics, f, indent=4) + + print("Metrics:", json.dumps(metrics, indent=2)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--program_path", default="initial.rs") + parser.add_argument("--results_dir", default="results") + args = parser.parse_args() + main(args.program_path, args.results_dir) diff --git a/examples/squeeze_hnsw/initial.rs b/examples/squeeze_hnsw/initial.rs new file mode 100644 index 0000000..d6b294c --- /dev/null +++ b/examples/squeeze_hnsw/initial.rs @@ -0,0 +1,431 @@ +use std::collections::{BinaryHeap, HashSet}; +use std::cmp::Ordering; +use std::env; +use std::fs; +use std::io::{self, BufRead, BufReader, Write}; + +/// Candidate for priority queues +#[derive(Clone, Copy, Debug)] +struct Candidate { + index: usize, + distance: f32, +} + +impl PartialEq for Candidate { + fn eq(&self, other: &Self) -> bool { + self.distance == other.distance && self.index == other.index + } +} +impl Eq for Candidate {} + +// MinHeap ordering (smallest distance first via reverse) +impl PartialOrd for Candidate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Candidate { + fn cmp(&self, other: &Self) -> Ordering { + other.distance.partial_cmp(&self.distance) + .unwrap_or(Ordering::Equal) + .then_with(|| other.index.cmp(&self.index)) + } +} + +/// MaxHeap wrapper +#[derive(Clone, Copy, Debug)] +struct MaxDistCandidate(Candidate); + +impl PartialEq for MaxDistCandidate { + fn eq(&self, other: &Self) -> bool { self.0.eq(&other.0) } +} +impl Eq for MaxDistCandidate {} +impl PartialOrd for MaxDistCandidate { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.distance.partial_cmp(&other.0.distance) + } +} +impl Ord for MaxDistCandidate { + fn cmp(&self, other: &Self) -> Ordering { + self.0.distance.partial_cmp(&other.0.distance) + .unwrap_or(Ordering::Equal) + } +} + +/// HNSW Node - stores neighbors at each layer +#[derive(Clone, Debug)] +struct Node { + links: Vec>, +} + +impl Node { + fn new(level: usize) -> Self { + let mut links = Vec::with_capacity(level + 1); + for _ in 0..=level { + links.push(Vec::new()); + } + Self { links } + } + + fn level(&self) -> usize { + if self.links.is_empty() { 0 } else { self.links.len() - 1 } + } +} + +/// Simple HNSW graph for k-NN search +struct Hnsw { + nodes: Vec, + entry_point: Option, + m: usize, + m_max0: usize, + ef_construction: usize, +} + +impl Hnsw { + fn new(m: usize, ef_construction: usize, capacity: usize) -> Self { + Self { + nodes: Vec::with_capacity(capacity), + entry_point: None, + m, + m_max0: m * 2, + ef_construction, + } + } + + fn get_random_level(&self, seed: usize) -> usize { + // Simple deterministic level assignment based on index + let level_mult = 1.0 / (self.m as f64).ln(); + let r = ((seed * 2654435761) % 1000000) as f64 / 1000000.0; + let level = (-r.ln() * level_mult).floor() as usize; + level.min(10) // Cap at 10 layers + } + + fn insert(&mut self, item_idx: usize, data: &[Vec]) { + let level = self.get_random_level(item_idx); + let mut new_node = Node::new(level); + + if item_idx >= self.nodes.len() { + self.nodes.resize(item_idx + 1, Node::new(0)); + } + + let entry_point = match self.entry_point { + Some(ep) => ep, + None => { + self.nodes[item_idx] = new_node; + self.entry_point = Some(item_idx); + return; + } + }; + + let max_level = self.nodes[entry_point].level(); + let mut curr_obj = entry_point; + let mut curr_dist = euclidean_distance(&data[item_idx], &data[curr_obj]); + + // Navigate through upper layers + for l in (level + 1..=max_level).rev() { + let mut changed = true; + while changed { + changed = false; + let current_node = &self.nodes[curr_obj]; + if l >= current_node.links.len() { break; } + + for &neighbor_idx in ¤t_node.links[l] { + let d = euclidean_distance(&data[item_idx], &data[neighbor_idx]); + if d < curr_dist { + curr_dist = d; + curr_obj = neighbor_idx; + changed = true; + } + } + } + } + + let mut ep = curr_obj; + + // Insert at each layer + for l in (0..=level.min(max_level)).rev() { + let candidates = self.search_layer(ep, item_idx, self.ef_construction, l, data); + + if let Some(c) = candidates.peek() { + ep = c.index; + } + + let m_allowed = if l == 0 { self.m_max0 } else { self.m }; + let selected = Self::select_neighbors(&candidates, m_allowed); + + new_node.links[l] = selected.clone(); + + // Add bidirectional links + for &neighbor_idx in &selected { + let mut neighbor_links = self.nodes[neighbor_idx].links[l].clone(); + neighbor_links.push(item_idx); + + let m_neigh_allowed = if l == 0 { self.m_max0 } else { self.m }; + if neighbor_links.len() > m_neigh_allowed { + neighbor_links = self.prune_connections(neighbor_idx, &neighbor_links, m_neigh_allowed, data); + } + + self.nodes[neighbor_idx].links[l] = neighbor_links; + } + } + + self.nodes[item_idx] = new_node; + + if level > max_level { + self.entry_point = Some(item_idx); + } + } + + // EVOLVE-BLOCK-START + /// Core beam search algorithm - this is the hot path to optimize + fn search_layer( + &self, + entry_point: usize, + query_idx: usize, + ef: usize, + level: usize, + data: &[Vec], + ) -> BinaryHeap { + let mut visited = HashSet::new(); + let d_ep = euclidean_distance(&data[query_idx], &data[entry_point]); + visited.insert(entry_point); + + // Candidates to explore (min-heap by distance) + let mut c_heap = BinaryHeap::new(); + c_heap.push(Candidate { index: entry_point, distance: d_ep }); + + // Best results so far (max-heap to track worst best) + let mut w_heap = BinaryHeap::new(); + w_heap.push(MaxDistCandidate(Candidate { index: entry_point, distance: d_ep })); + + while let Some(c) = c_heap.pop() { + let f = w_heap.peek().unwrap(); + + // Stop if closest candidate is worse than worst result + if c.distance > f.0.distance { + break; + } + + let c_node = &self.nodes[c.index]; + if level >= c_node.links.len() { continue; } + + // Explore neighbors + for &neighbor_idx in &c_node.links[level] { + if !visited.contains(&neighbor_idx) { + visited.insert(neighbor_idx); + + let d = euclidean_distance(&data[query_idx], &data[neighbor_idx]); + let f_curr = w_heap.peek().unwrap(); + + // Add if better than worst or we have room + if d < f_curr.0.distance || w_heap.len() < ef { + let cand = Candidate { index: neighbor_idx, distance: d }; + c_heap.push(cand); + w_heap.push(MaxDistCandidate(cand)); + + // Maintain ef size + if w_heap.len() > ef { + w_heap.pop(); + } + } + } + } + } + + // Convert to result heap + let mut result = BinaryHeap::new(); + for wrapper in w_heap { + result.push(wrapper.0); + } + result + } + // EVOLVE-BLOCK-END + + fn select_neighbors(candidates: &BinaryHeap, m: usize) -> Vec { + let mut sorted: Vec<_> = candidates.iter().copied().collect(); + sorted.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap_or(Ordering::Equal)); + sorted.into_iter().take(m).map(|c| c.index).collect() + } + + fn prune_connections( + &self, + node_idx: usize, + neighbors: &[usize], + m_max: usize, + data: &[Vec], + ) -> Vec { + let mut candidates: BinaryHeap = BinaryHeap::new(); + for &neigh in neighbors { + let d = euclidean_distance(&data[node_idx], &data[neigh]); + candidates.push(Candidate { index: neigh, distance: d }); + } + Self::select_neighbors(&candidates, m_max) + } + + /// Main search function - find k nearest neighbors + fn search(&self, query: &[f32], k: usize, ef: usize, data: &[Vec]) -> Vec<(usize, f32)> { + if self.entry_point.is_none() { return Vec::new(); } + let entry_point = self.entry_point.unwrap(); + + let dist_to_query = |idx: usize| euclidean_distance(query, &data[idx]); + + let mut curr_obj = entry_point; + let mut curr_dist = dist_to_query(curr_obj); + + let max_level = self.nodes[entry_point].level(); + + // Navigate upper layers greedily + for l in (1..=max_level).rev() { + let mut changed = true; + while changed { + changed = false; + let current_node = &self.nodes[curr_obj]; + if l >= current_node.links.len() { break; } + + for &neighbor_idx in ¤t_node.links[l] { + let d = dist_to_query(neighbor_idx); + if d < curr_dist { + curr_dist = d; + curr_obj = neighbor_idx; + changed = true; + } + } + } + } + + // Search at base layer + let mut visited = HashSet::new(); + let mut c_heap = BinaryHeap::new(); + let mut w_heap = BinaryHeap::new(); + + visited.insert(curr_obj); + c_heap.push(Candidate { index: curr_obj, distance: curr_dist }); + w_heap.push(MaxDistCandidate(Candidate { index: curr_obj, distance: curr_dist })); + + while let Some(c) = c_heap.pop() { + let f = w_heap.peek().unwrap(); + if c.distance > f.0.distance { break; } + + let c_node = &self.nodes[c.index]; + if c_node.links.is_empty() { continue; } + + for &neighbor_idx in &c_node.links[0] { + if !visited.contains(&neighbor_idx) { + visited.insert(neighbor_idx); + let d = dist_to_query(neighbor_idx); + let f_curr = w_heap.peek().unwrap(); + + if d < f_curr.0.distance || w_heap.len() < ef { + let cand = Candidate { index: neighbor_idx, distance: d }; + c_heap.push(cand); + w_heap.push(MaxDistCandidate(cand)); + if w_heap.len() > ef { + w_heap.pop(); + } + } + } + } + } + + let mut result_vec: Vec<_> = w_heap.into_iter().map(|w| (w.0.index, w.0.distance)).collect(); + result_vec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)); + result_vec.truncate(k); + result_vec + } +} + +/// Euclidean distance between two vectors +fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + a.iter() + .zip(b.iter()) + .map(|(x, y)| (x - y).powi(2)) + .sum::() + .sqrt() +} + +/// Read dataset from file +fn read_data(path: &str) -> io::Result>> { + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + let mut data = Vec::new(); + + for line in reader.lines() { + let line = line?; + if line.trim().is_empty() { continue; } + let vec: Vec = line.split_whitespace() + .filter_map(|s| s.parse().ok()) + .collect(); + if !vec.is_empty() { + data.push(vec); + } + } + Ok(data) +} + +/// Read queries from file +fn read_queries(path: &str) -> io::Result>> { + read_data(path) +} + +/// Write results to JSON +fn write_results(path: &str, results: &[Vec<(usize, f32)>]) -> io::Result<()> { + let mut file = fs::File::create(path)?; + write!(file, "[")?; + for (i, result) in results.iter().enumerate() { + if i > 0 { write!(file, ",")?; } + write!(file, "[")?; + for (j, (idx, dist)) in result.iter().enumerate() { + if j > 0 { write!(file, ",")?; } + write!(file, "[{},{}]", idx, dist)?; + } + write!(file, "]")?; + } + write!(file, "]")?; + Ok(()) +} + +fn main() -> io::Result<()> { + let args: Vec = env::args().collect(); + if args.len() != 4 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let data_path = &args[1]; + let query_path = &args[2]; + let output_path = &args[3]; + + // Read data and queries + let data = read_data(data_path)?; + let queries = read_queries(query_path)?; + + if data.is_empty() { + eprintln!("Empty dataset"); + std::process::exit(1); + } + + // Build HNSW index + let m = 16; + let ef_construction = 100; + let mut hnsw = Hnsw::new(m, ef_construction, data.len()); + + for i in 0..data.len() { + hnsw.insert(i, &data); + } + + // Search for each query + let k = 10; + let ef_search = 50; + let mut results = Vec::with_capacity(queries.len()); + + for query in &queries { + let neighbors = hnsw.search(query, k, ef_search, &data); + results.push(neighbors); + } + + // Write results + write_results(output_path, &results)?; + + Ok(()) +} diff --git a/examples/squeeze_tsne/evaluate.py b/examples/squeeze_tsne/evaluate.py new file mode 100644 index 0000000..91d5d1c --- /dev/null +++ b/examples/squeeze_tsne/evaluate.py @@ -0,0 +1,206 @@ +""" +Evaluation script for t-SNE gradient optimization. + +This evaluates the quality of t-SNE embeddings using: +1. Trustworthiness: How well local neighborhoods are preserved +2. Speed: Time to compute the embedding + +Combined score = trustworthiness * speed_factor * 100 +""" + +import json +import os +import subprocess +import tempfile +import time +from pathlib import Path + +import numpy as np + + +def generate_test_data(n_samples: int = 200, n_features: int = 50, n_clusters: int = 5, seed: int = 42) -> np.ndarray: + """Generate clustered test data for t-SNE evaluation.""" + np.random.seed(seed) + + samples_per_cluster = n_samples // n_clusters + data = [] + + for i in range(n_clusters): + # Cluster center + center = np.random.randn(n_features) * 10 + # Points around center + cluster_points = center + np.random.randn(samples_per_cluster, n_features) * 0.5 + data.append(cluster_points) + + return np.vstack(data) + + +def compile_rust_program(rust_file: str, output_binary: str) -> tuple[bool, str]: + """Compile Rust program with optimizations.""" + try: + result = subprocess.run( + ["rustc", "-O", "-o", output_binary, rust_file], + capture_output=True, + text=True, + timeout=120, + ) + if result.returncode != 0: + return False, f"Compilation failed:\n{result.stderr}" + return True, "" + except subprocess.TimeoutExpired: + return False, "Compilation timed out" + except FileNotFoundError: + return False, "rustc not found" + + +def run_tsne_benchmark( + binary_path: str, + data: np.ndarray, + output_file: str, + timeout: int = 300, +) -> tuple[dict | None, float, str | None]: + """Run t-SNE and return results.""" + # Write data to temp file + data_file = output_file.replace(".json", "_data.txt") + np.savetxt(data_file, data, fmt="%.6f") + + try: + start_time = time.time() + result = subprocess.run( + [binary_path, data_file, output_file], + capture_output=True, + text=True, + timeout=timeout, + ) + elapsed = time.time() - start_time + + if result.returncode != 0: + return None, elapsed, f"Execution failed:\n{result.stderr}" + + # Parse results + with open(output_file, "r") as f: + results = json.load(f) + + return results, elapsed, None + + except subprocess.TimeoutExpired: + return None, timeout, "Execution timed out" + except json.JSONDecodeError as e: + return None, 0.0, f"Failed to parse output: {e}" + finally: + # Cleanup data file + if os.path.exists(data_file): + os.remove(data_file) + + +def main(program_path: str, results_dir: str): + """Main evaluation function for Genesis.""" + os.makedirs(results_dir, exist_ok=True) + + metrics_file = os.path.join(results_dir, "metrics.json") + correct_file = os.path.join(results_dir, "correct.json") + + # Compile the Rust program + binary_path = os.path.join(results_dir, "tsne_binary") + success, error = compile_rust_program(program_path, binary_path) + + if not success: + with open(metrics_file, "w") as f: + json.dump({"combined_score": 0.0, "error": error}, f, indent=2) + with open(correct_file, "w") as f: + json.dump({"correct": False, "error": error}, f, indent=2) + return {"combined_score": 0.0}, False, error + + # Run multiple evaluations with different data sizes + all_trustworthiness = [] + all_times = [] + errors = [] + + test_configs = [ + {"n_samples": 100, "n_features": 20, "seed": 42}, + {"n_samples": 150, "n_features": 30, "seed": 123}, + {"n_samples": 200, "n_features": 40, "seed": 456}, + ] + + for config in test_configs: + data = generate_test_data( + n_samples=config["n_samples"], + n_features=config["n_features"], + n_clusters=5, + seed=config["seed"], + ) + + output_file = os.path.join(results_dir, f"tsne_output_{config['seed']}.json") + results, elapsed, error = run_tsne_benchmark(binary_path, data, output_file) + + if error: + errors.append(error) + continue + + trust = results.get("trustworthiness", 0.0) + all_trustworthiness.append(trust) + all_times.append(elapsed) + + # Calculate combined score + if not all_trustworthiness: + combined_score = 0.0 + mean_trust = 0.0 + mean_time = 0.0 + is_correct = False + final_error = "; ".join(errors) if errors else "No successful runs" + else: + mean_trust = np.mean(all_trustworthiness) + mean_time = np.mean(all_times) + + # Trustworthiness should be > 0.5 for a valid embedding + if mean_trust < 0.3: + is_correct = False + final_error = f"Trustworthiness too low: {mean_trust:.4f}" + combined_score = 0.0 + else: + is_correct = True + final_error = None + + # Speed factor: faster = better (baseline ~10s for n=200) + baseline_time = 10.0 + speed_factor = min(2.0, baseline_time / max(mean_time, 0.1)) + + # Combined score: trustworthiness * speed_factor * 100 + combined_score = float(mean_trust * speed_factor * 100) + + # Save metrics + metrics = { + "combined_score": combined_score, + "public": { + "mean_trustworthiness": float(mean_trust), + "mean_time_seconds": float(mean_time), + "num_successful_runs": len(all_trustworthiness), + }, + "private": { + "all_trustworthiness": [float(t) for t in all_trustworthiness], + "all_times": [float(t) for t in all_times], + "errors": errors, + }, + } + + with open(metrics_file, "w") as f: + json.dump(metrics, f, indent=2) + + with open(correct_file, "w") as f: + json.dump({"correct": is_correct, "error": final_error}, f, indent=2) + + return metrics, is_correct, final_error + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Evaluate t-SNE gradient optimization") + parser.add_argument("--program_path", required=True, help="Path to Rust program") + parser.add_argument("--results_dir", required=True, help="Directory for results") + args = parser.parse_args() + + metrics, correct, error = main(args.program_path, args.results_dir) + print(f"Score: {metrics['combined_score']:.2f}, Correct: {correct}") + if error: + print(f"Error: {error}") diff --git a/examples/squeeze_tsne/initial.rs b/examples/squeeze_tsne/initial.rs new file mode 100644 index 0000000..c328a23 --- /dev/null +++ b/examples/squeeze_tsne/initial.rs @@ -0,0 +1,509 @@ +// Standalone t-SNE gradient implementation for Genesis optimization +// This file can be compiled with: rustc -O initial.rs -o tsne +// +// The gradient computation is O(n^2) and called 1000+ times per embedding. +// Optimizing this is critical for t-SNE performance. + +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; + +// Simple 2D array using flat Vec +struct Array2 { + data: Vec, + rows: usize, + cols: usize, +} + +impl Array2 { + fn zeros(rows: usize, cols: usize) -> Self { + Self { + data: vec![0.0; rows * cols], + rows, + cols, + } + } + + fn get(&self, i: usize, j: usize) -> f64 { + self.data[i * self.cols + j] + } + + fn set(&mut self, i: usize, j: usize, val: f64) { + self.data[i * self.cols + j] = val; + } +} + +// EVOLVE-BLOCK-START +/// Compute t-SNE gradient for a single point +/// +/// This is the hottest code path - called n times per iteration, 1000+ iterations. +/// The gradient formula is: grad_i = 4 * sum_j[(p_ij - q_ij) * (1 + ||y_i - y_j||^2)^-1 * (y_i - y_j)] +/// +/// Optimization opportunities: +/// 1. Loop unrolling for n_components (typically 2) +/// 2. SIMD for distance computation +/// 3. Early termination when contribution is negligible +/// 4. Cache-friendly memory access patterns +/// 5. Pre-compute repeated values +fn compute_gradient_for_point( + point_idx: usize, + n_points: usize, + n_components: usize, + pq: &Array2, // P - Q matrix (n x n) + y: &Array2, // Current embedding (n x n_components) +) -> Vec { + let mut grad = vec![0.0; n_components]; + + for j in 0..n_points { + if point_idx == j { + continue; + } + + // Compute squared distance in embedding space + let mut dist_sq = 0.0; + for k in 0..n_components { + let diff = y.get(point_idx, k) - y.get(j, k); + dist_sq += diff * diff; + } + + // Student t-distribution kernel: (1 + d^2)^-1 + let kernel = 1.0 / (1.0 + dist_sq); + + // Gradient contribution from this pair + let mult = 4.0 * pq.get(point_idx, j) * kernel; + + for k in 0..n_components { + grad[k] += mult * (y.get(point_idx, k) - y.get(j, k)); + } + } + + grad +} + +/// Compute full gradient matrix +/// Returns gradient for all points (n x n_components) +fn compute_gradient( + n_points: usize, + n_components: usize, + p: &Array2, // Joint probabilities in high-d (n x n) + q: &Array2, // Joint probabilities in low-d (n x n) + y: &Array2, // Current embedding (n x n_components) +) -> Array2 { + // Pre-compute P - Q + let mut pq = Array2::zeros(n_points, n_points); + for i in 0..n_points { + for j in 0..n_points { + pq.set(i, j, p.get(i, j) - q.get(i, j)); + } + } + + // Compute gradient for each point + let mut grad = Array2::zeros(n_points, n_components); + for i in 0..n_points { + let point_grad = compute_gradient_for_point(i, n_points, n_components, &pq, y); + for k in 0..n_components { + grad.set(i, k, point_grad[k]); + } + } + + grad +} +// EVOLVE-BLOCK-END + +/// Compute Q distribution (Student t-distribution in low-d space) +fn compute_q(y: &Array2, n_points: usize, n_components: usize) -> Array2 { + let mut q = Array2::zeros(n_points, n_points); + let mut sum_q = 0.0; + + // Compute unnormalized Q + for i in 0..n_points { + for j in (i + 1)..n_points { + let mut dist_sq = 0.0; + for k in 0..n_components { + let diff = y.get(i, k) - y.get(j, k); + dist_sq += diff * diff; + } + let qij = 1.0 / (1.0 + dist_sq); + q.set(i, j, qij); + q.set(j, i, qij); + sum_q += 2.0 * qij; + } + } + + // Normalize + if sum_q > 0.0 { + for i in 0..n_points { + for j in 0..n_points { + let val = q.get(i, j) / sum_q; + q.set(i, j, val.max(1e-12)); + } + } + } + + q +} + +/// Compute pairwise Euclidean distances (squared) +fn compute_pairwise_distances(data: &[Vec]) -> Array2 { + let n = data.len(); + let mut distances = Array2::zeros(n, n); + + for i in 0..n { + for j in (i + 1)..n { + let mut dist_sq = 0.0; + for k in 0..data[i].len() { + let diff = data[i][k] - data[j][k]; + dist_sq += diff * diff; + } + distances.set(i, j, dist_sq); + distances.set(j, i, dist_sq); + } + } + + distances +} + +/// Compute joint probabilities P using perplexity-based binary search +fn compute_joint_probabilities(distances: &Array2, n_points: usize, perplexity: f64) -> Array2 { + let target_entropy = perplexity.ln(); + let mut p = Array2::zeros(n_points, n_points); + + // Compute P(j|i) for each i + for i in 0..n_points { + let mut beta = 1.0; + let mut beta_min = f64::NEG_INFINITY; + let mut beta_max = f64::INFINITY; + + let mut p_row = vec![0.0; n_points]; + + // Binary search for sigma + for _ in 0..50 { + let mut sum_p = 0.0; + for j in 0..n_points { + if i != j { + let pij = (-beta * distances.get(i, j)).exp(); + p_row[j] = pij; + sum_p += pij; + } + } + + // Normalize + if sum_p > 1e-10 { + for j in 0..n_points { + p_row[j] /= sum_p; + } + } + + // Compute entropy + let mut entropy = 0.0; + for j in 0..n_points { + if p_row[j] > 1e-10 { + entropy -= p_row[j] * p_row[j].ln(); + } + } + + let entropy_diff = entropy - target_entropy; + if entropy_diff.abs() < 1e-5 { + break; + } + + if entropy_diff > 0.0 { + beta_min = beta; + beta = if beta_max.is_infinite() { beta * 2.0 } else { (beta + beta_max) / 2.0 }; + } else { + beta_max = beta; + beta = if beta_min.is_infinite() { beta / 2.0 } else { (beta + beta_min) / 2.0 }; + } + } + + for j in 0..n_points { + p.set(i, j, p_row[j]); + } + } + + // Symmetrize: P = (P + P^T) / (2n) + let mut p_sym = Array2::zeros(n_points, n_points); + for i in 0..n_points { + for j in 0..n_points { + let val = (p.get(i, j) + p.get(j, i)) / (2.0 * n_points as f64); + p_sym.set(i, j, val.max(1e-12)); + } + } + + p_sym +} + +/// Simple LCG random number generator (seeded for reproducibility) +struct Rng { + state: u64, +} + +impl Rng { + fn new(seed: u64) -> Self { + Self { state: seed } + } + + fn next_f64(&mut self) -> f64 { + // LCG parameters (same as glibc) + self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345); + (self.state as f64) / (u64::MAX as f64) + } + + fn normal(&mut self) -> f64 { + // Box-Muller transform + let u1 = self.next_f64().max(1e-10); + let u2 = self.next_f64(); + (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos() + } +} + +/// Run t-SNE embedding +fn run_tsne( + data: &[Vec], + n_components: usize, + perplexity: f64, + n_iter: usize, + learning_rate: f64, + seed: u64, +) -> Array2 { + let n_points = data.len(); + + // Compute pairwise distances + let distances = compute_pairwise_distances(data); + + // Compute joint probabilities P + let p = compute_joint_probabilities(&distances, n_points, perplexity); + + // Initialize embedding randomly + let mut rng = Rng::new(seed); + let mut y = Array2::zeros(n_points, n_components); + for i in 0..n_points { + for k in 0..n_components { + y.set(i, k, rng.normal() * 1e-4); + } + } + + // Gradient descent parameters + let early_exaggeration = 12.0; + let momentum = 0.5; + let final_momentum = 0.8; + let momentum_switch_iter = 250; + + let mut gains = vec![vec![1.0; n_components]; n_points]; + let mut y_incs = Array2::zeros(n_points, n_components); + + for iter in 0..n_iter { + // Scale P during early exaggeration + let p_scaled = if iter < 250 { + let mut ps = Array2::zeros(n_points, n_points); + for i in 0..n_points { + for j in 0..n_points { + ps.set(i, j, p.get(i, j) * early_exaggeration); + } + } + ps + } else { + let mut ps = Array2::zeros(n_points, n_points); + for i in 0..n_points { + for j in 0..n_points { + ps.set(i, j, p.get(i, j)); + } + } + ps + }; + + // Compute Q distribution + let q = compute_q(&y, n_points, n_components); + + // Compute gradient (this is the hot path we're optimizing!) + let grad = compute_gradient(n_points, n_components, &p_scaled, &q, &y); + + // Update gains + for i in 0..n_points { + for k in 0..n_components { + let sign_match = (grad.get(i, k) > 0.0) == (y_incs.get(i, k) > 0.0); + gains[i][k] = if sign_match { + f64::max(gains[i][k] * 0.8, 0.01) + } else { + gains[i][k] + 0.2 + }; + } + } + + // Update momentum + let current_momentum = if iter < momentum_switch_iter { + momentum + } else { + final_momentum + }; + + // Update positions + for i in 0..n_points { + for k in 0..n_components { + let inc = current_momentum * y_incs.get(i, k) - learning_rate * gains[i][k] * grad.get(i, k); + y_incs.set(i, k, inc); + y.set(i, k, y.get(i, k) + inc); + } + } + + // Center embedding + let mut mean = vec![0.0; n_components]; + for i in 0..n_points { + for k in 0..n_components { + mean[k] += y.get(i, k); + } + } + for k in 0..n_components { + mean[k] /= n_points as f64; + } + for i in 0..n_points { + for k in 0..n_components { + y.set(i, k, y.get(i, k) - mean[k]); + } + } + } + + y +} + +/// Compute trustworthiness metric (measures how well local structure is preserved) +fn compute_trustworthiness( + high_d_data: &[Vec], + embedding: &Array2, + k: usize, +) -> f64 { + let n = high_d_data.len(); + let n_components = embedding.cols; + + // Get k-nearest neighbors in original space + let mut high_d_neighbors: Vec> = Vec::new(); + for i in 0..n { + let mut dists: Vec<(usize, f64)> = (0..n) + .filter(|&j| j != i) + .map(|j| { + let mut d = 0.0; + for dim in 0..high_d_data[i].len() { + let diff = high_d_data[i][dim] - high_d_data[j][dim]; + d += diff * diff; + } + (j, d) + }) + .collect(); + dists.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + high_d_neighbors.push(dists.iter().take(k).map(|(idx, _)| *idx).collect()); + } + + // Get k-nearest neighbors in embedding space + let mut low_d_neighbors: Vec> = Vec::new(); + for i in 0..n { + let mut dists: Vec<(usize, f64)> = (0..n) + .filter(|&j| j != i) + .map(|j| { + let mut d = 0.0; + for dim in 0..n_components { + let diff = embedding.get(i, dim) - embedding.get(j, dim); + d += diff * diff; + } + (j, d) + }) + .collect(); + dists.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + low_d_neighbors.push(dists.iter().take(k).map(|(idx, _)| *idx).collect()); + } + + // Compute trustworthiness + let mut error = 0.0; + for i in 0..n { + let high_d_set: std::collections::HashSet<_> = high_d_neighbors[i].iter().collect(); + for (rank, &j) in low_d_neighbors[i].iter().enumerate() { + if !high_d_set.contains(&j) { + // Find rank in original space + let orig_rank = (0..n) + .filter(|&m| m != i) + .position(|m| { + let mut d = 0.0; + for dim in 0..high_d_data[i].len() { + let diff = high_d_data[i][dim] - high_d_data[m][dim]; + d += diff * diff; + } + // Check if this is the j-th point + m == j + }) + .unwrap_or(n - 1); + error += (orig_rank.saturating_sub(k)) as f64; + } + } + } + + let norm = (2.0 / (n as f64 * k as f64 * (2.0 * n as f64 - 3.0 * k as f64 - 1.0))).max(1e-10); + 1.0 - norm * error +} + +fn main() { + let args: Vec = std::env::args().collect(); + if args.len() < 3 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let data_file = &args[1]; + let output_file = &args[2]; + + // Load data + let file = File::open(data_file).expect("Failed to open data file"); + let reader = BufReader::new(file); + + let mut data: Vec> = Vec::new(); + for line in reader.lines() { + let line = line.expect("Failed to read line"); + if line.trim().is_empty() { + continue; + } + let values: Vec = line + .split_whitespace() + .filter_map(|s| s.parse().ok()) + .collect(); + if !values.is_empty() { + data.push(values); + } + } + + let n_points = data.len(); + let n_dims = if data.is_empty() { 0 } else { data[0].len() }; + + eprintln!("Loaded {} points with {} dimensions", n_points, n_dims); + + // Run t-SNE + let start = std::time::Instant::now(); + let embedding = run_tsne( + &data, + 2, // n_components + 30.0, // perplexity + 500, // n_iter (reduced for testing) + 200.0, // learning_rate + 42, // seed + ); + let elapsed = start.elapsed(); + + eprintln!("t-SNE completed in {:.3}s", elapsed.as_secs_f64()); + + // Compute trustworthiness + let trust = compute_trustworthiness(&data, &embedding, 10); + eprintln!("Trustworthiness (k=10): {:.4}", trust); + + // Write output as JSON + let mut output = File::create(output_file).expect("Failed to create output file"); + writeln!(output, "{{").unwrap(); + writeln!(output, " \"embedding\": [").unwrap(); + for i in 0..n_points { + let mut row = Vec::new(); + for k in 0..2 { + row.push(format!("{:.6}", embedding.get(i, k))); + } + let comma = if i < n_points - 1 { "," } else { "" }; + writeln!(output, " [{}]{}", row.join(", "), comma).unwrap(); + } + writeln!(output, " ],").unwrap(); + writeln!(output, " \"trustworthiness\": {:.6},", trust).unwrap(); + writeln!(output, " \"time_seconds\": {:.6}", elapsed.as_secs_f64()).unwrap(); + writeln!(output, "}}").unwrap(); +} From 104aaba604f34bcea8df96762a618a103a57e606 Mon Sep 17 00:00:00 2001 From: George Pearse Date: Mon, 24 Nov 2025 08:57:45 -0500 Subject: [PATCH 2/2] feat: implement React frontend matching legacy viz capabilities, update E2B SDK usage, and add documentation --- .gitignore | 2 + .mcp.json | 10 + configs/cluster/e2b.yaml | 26 +- configs/variant/circle_packing_e2b.yaml | 31 + docs/api/core/config.md | 3 + docs/api/core/runner.md | 3 + docs/api/database.md | 5 + docs/api/launch.md | 7 + docs/developer_guide.md | 95 + docs/getting_started.md | 2 +- docs/index.md | 328 ++ docs/papers.md | 385 ++ docs/roadmap.md | 135 + .../circle_packing/evaluate_standalone.py | 186 + examples/e2b_demo/README.md | 185 + examples/e2b_demo/demo_e2b_backend.py | 418 ++ examples/e2b_demo/run_circle_packing_e2b.py | 164 + genesis/launch/e2b.py | 7 +- genesis/webui/frontend/package-lock.json | 3748 ++++++++++++++++- genesis/webui/frontend/package.json | 15 +- genesis/webui/frontend/src/App.css | 3 +- genesis/webui/frontend/src/App.tsx | 20 +- .../src/components/DatabaseSelector.css | 87 + .../src/components/DatabaseSelector.tsx | 133 + .../src/components/VisualizationLayout.css | 57 + .../src/components/VisualizationLayout.tsx | 67 + .../components/left-panel/BestPathView.css | 101 + .../components/left-panel/BestPathView.tsx | 78 + .../components/left-panel/ClustersView.css | 22 + .../components/left-panel/ClustersView.tsx | 170 + .../components/left-panel/EmbeddingsView.css | 33 + .../components/left-panel/EmbeddingsView.tsx | 333 ++ .../src/components/left-panel/IslandsView.css | 15 + .../src/components/left-panel/IslandsView.tsx | 125 + .../src/components/left-panel/LeftPanel.css | 76 + .../src/components/left-panel/LeftPanel.tsx | 82 + .../src/components/left-panel/MetricsView.css | 36 + .../src/components/left-panel/MetricsView.tsx | 152 + .../left-panel/ModelPosteriorsView.css | 50 + .../left-panel/ModelPosteriorsView.tsx | 257 ++ .../components/left-panel/ProgramsTable.css | 90 + .../components/left-panel/ProgramsTable.tsx | 156 + .../src/components/left-panel/TreeView.css | 98 + .../src/components/left-panel/TreeView.tsx | 407 ++ .../right-panel/CodeViewerPanel.css | 80 + .../right-panel/CodeViewerPanel.tsx | 98 + .../right-panel/DiffViewerPanel.css | 107 + .../right-panel/DiffViewerPanel.tsx | 91 + .../right-panel/EvaluationPanel.css | 86 + .../right-panel/EvaluationPanel.tsx | 82 + .../components/right-panel/LLMResultPanel.css | 90 + .../components/right-panel/LLMResultPanel.tsx | 87 + .../components/right-panel/MetaInfoPanel.css | 80 + .../components/right-panel/MetaInfoPanel.tsx | 203 + .../right-panel/NodeDetailsPanel.css | 82 + .../right-panel/NodeDetailsPanel.tsx | 102 + .../right-panel/ParetoFrontPanel.css | 134 + .../right-panel/ParetoFrontPanel.tsx | 441 ++ .../src/components/right-panel/RightPanel.css | 90 + .../src/components/right-panel/RightPanel.tsx | 100 + .../right-panel/ScratchpadPanel.css | 149 + .../right-panel/ScratchpadPanel.tsx | 212 + .../frontend/src/context/GenesisContext.tsx | 294 ++ genesis/webui/frontend/src/index.css | 37 +- genesis/webui/frontend/src/services/api.ts | 56 + genesis/webui/frontend/src/types/index.ts | 117 + .../frontend/src/types/react-plotly.d.ts | 42 + genesis/webui/frontend/src/utils/pareto.ts | 173 + genesis/webui/frontend/vite.config.ts | 15 +- mkdocs.yml | 73 + 70 files changed, 11363 insertions(+), 161 deletions(-) create mode 100644 .mcp.json create mode 100644 configs/variant/circle_packing_e2b.yaml create mode 100644 docs/api/core/config.md create mode 100644 docs/api/core/runner.md create mode 100644 docs/api/database.md create mode 100644 docs/api/launch.md create mode 100644 docs/developer_guide.md create mode 100644 docs/index.md create mode 100644 docs/papers.md create mode 100644 docs/roadmap.md create mode 100644 examples/circle_packing/evaluate_standalone.py create mode 100644 examples/e2b_demo/README.md create mode 100644 examples/e2b_demo/demo_e2b_backend.py create mode 100644 examples/e2b_demo/run_circle_packing_e2b.py create mode 100644 genesis/webui/frontend/src/components/DatabaseSelector.css create mode 100644 genesis/webui/frontend/src/components/DatabaseSelector.tsx create mode 100644 genesis/webui/frontend/src/components/VisualizationLayout.css create mode 100644 genesis/webui/frontend/src/components/VisualizationLayout.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/BestPathView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/BestPathView.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/ClustersView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/ClustersView.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/EmbeddingsView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/EmbeddingsView.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/IslandsView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/IslandsView.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/LeftPanel.css create mode 100644 genesis/webui/frontend/src/components/left-panel/LeftPanel.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/MetricsView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/MetricsView.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/ProgramsTable.css create mode 100644 genesis/webui/frontend/src/components/left-panel/ProgramsTable.tsx create mode 100644 genesis/webui/frontend/src/components/left-panel/TreeView.css create mode 100644 genesis/webui/frontend/src/components/left-panel/TreeView.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/EvaluationPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/EvaluationPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/LLMResultPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/LLMResultPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/RightPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/RightPanel.tsx create mode 100644 genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.css create mode 100644 genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.tsx create mode 100644 genesis/webui/frontend/src/context/GenesisContext.tsx create mode 100644 genesis/webui/frontend/src/services/api.ts create mode 100644 genesis/webui/frontend/src/types/index.ts create mode 100644 genesis/webui/frontend/src/types/react-plotly.d.ts create mode 100644 genesis/webui/frontend/src/utils/pareto.ts create mode 100644 mkdocs.yml diff --git a/.gitignore b/.gitignore index 496d3d4..c9b542f 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,5 @@ cython_debug/ # PyPI configuration file .pypirc .pre-commit-cache/ +results/ +*.log diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..d284355 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "genesis": { + "type": "stdio", + "command": "python3", + "args": ["-m", "genesis.mcp_server"], + "cwd": "/Users/georgepearse/Genesis" + } + } +} diff --git a/configs/cluster/e2b.yaml b/configs/cluster/e2b.yaml index 496b0c4..57b6760 100644 --- a/configs/cluster/e2b.yaml +++ b/configs/cluster/e2b.yaml @@ -1,5 +1,6 @@ +# @package _global_ # E2B Cloud Sandbox Configuration -# +# # This configuration enables running Genesis evaluations in E2B cloud sandboxes. # E2B provides isolated, ephemeral environments for code execution. # @@ -15,30 +16,29 @@ job_config: _target_: genesis.launch.E2BJobConfig - eval_program_path: ${distributed_job_config.eval_program_path} - + # Override this in variant config for task-specific evaluator + eval_program_path: evaluate.py + # E2B sandbox template to use # Options: "base" (Python), or custom templates you've created template: "base" - + # Sandbox timeout in seconds (max time for each evaluation) timeout: 300 - + # Python dependencies to install in each sandbox # These are installed via pip before running the evaluation + # NOTE: genesis-evolve is not on PyPI - use standalone evaluators dependencies: - numpy - - scipy - + # Additional files to upload to the sandbox # Map of sandbox_path -> local_path - # additional_files: - # "/home/user/data.json": "data/input.json" - + additional_files: {} + # Environment variables to set in the sandbox - # env_vars: - # MY_VAR: "my_value" - + env_vars: {} + evo_config: job_type: "e2b" # E2B supports high parallelism - adjust based on your E2B plan limits diff --git a/configs/variant/circle_packing_e2b.yaml b/configs/variant/circle_packing_e2b.yaml new file mode 100644 index 0000000..12f2354 --- /dev/null +++ b/configs/variant/circle_packing_e2b.yaml @@ -0,0 +1,31 @@ +# @package _global_ +# Circle Packing with E2B Cloud Sandboxes +# +# This variant runs circle packing optimization using E2B for evaluation. +# Each evaluation runs in an isolated cloud sandbox. + +defaults: + - override /database@_global_: island_small + - override /evolution@_global_: small_budget + - override /task@_global_: circle_packing + - override /cluster@_global_: e2b + - _self_ + +job_config: + _target_: genesis.launch.E2BJobConfig + # Use the standalone evaluator (no genesis package required) + eval_program_path: examples/circle_packing/evaluate_standalone.py + template: "base" + timeout: 120 + # Only numpy needed - no genesis package required! + dependencies: + - numpy + additional_files: {} + env_vars: {} + +evo_config: + job_type: "e2b" + num_generations: 20 + max_parallel_jobs: 5 + +variant_suffix: "_e2b" diff --git a/docs/api/core/config.md b/docs/api/core/config.md new file mode 100644 index 0000000..cba26ca --- /dev/null +++ b/docs/api/core/config.md @@ -0,0 +1,3 @@ +# Evolution Config + +::: genesis.core.EvolutionConfig diff --git a/docs/api/core/runner.md b/docs/api/core/runner.md new file mode 100644 index 0000000..47ac8b6 --- /dev/null +++ b/docs/api/core/runner.md @@ -0,0 +1,3 @@ +# Core Runner + +::: genesis.core.EvolutionRunner diff --git a/docs/api/database.md b/docs/api/database.md new file mode 100644 index 0000000..30eb6fb --- /dev/null +++ b/docs/api/database.md @@ -0,0 +1,5 @@ +# Database + +::: genesis.database.DatabaseConfig +::: genesis.database.ProgramDatabase +::: genesis.database.Program diff --git a/docs/api/launch.md b/docs/api/launch.md new file mode 100644 index 0000000..ba25c9d --- /dev/null +++ b/docs/api/launch.md @@ -0,0 +1,7 @@ +# Launch Configuration + +::: genesis.launch.JobConfig +::: genesis.launch.LocalJobConfig +::: genesis.launch.SlurmDockerJobConfig +::: genesis.launch.SlurmCondaJobConfig +::: genesis.launch.E2BJobConfig diff --git a/docs/developer_guide.md b/docs/developer_guide.md new file mode 100644 index 0000000..466df37 --- /dev/null +++ b/docs/developer_guide.md @@ -0,0 +1,95 @@ +# Repository Guidelines + +**Important: All pull requests should be opened against this repository: https://github.com/GeorgePearse/Genesis** + +## Project Structure & Module Organization +- `genesis/`: core Python package for evolution runners, job configs, and database utilities. +- `configs/`: Hydra configuration presets; start new experiments by extending an existing YAML here. +- `tests/`: pytest suite covering edit strategies and evaluation tooling; mirror source layout when adding modules. +- `examples/`: runnable task templates, including notebooks, to demonstrate common workflows. +- `webui-react/` and `website/`: front-end clients; keep UI changes isolated from the core engine. + +## Build, Test, and Development Commands +- `uv venv --python 3.12 && source .venv/bin/activate`: create and activate the recommended environment. +- `uv pip install -e .[dev]`: install the package with developer tooling. +- `genesis_launch variant=circle_packing_example`: run a reference experiment via the CLI entrypoint. +- `pytest`: execute the full unit test suite; use `pytest tests/test_edit_circle.py -k "smoke"` while iterating. +- `black genesis tests && isort genesis tests && flake8 genesis tests`: format and lint before submitting. + +## Coding Style & Naming Conventions +- Python 3.12+, 4-space indentation, and type hints for new public APIs. +- Modules, files, and functions use `snake_case`; classes follow `PascalCase`. +- Keep experiment configs declarative; prefer Hydra overrides (`genesis_launch +foo=bar`) over ad-hoc scripts. +- Document non-obvious behaviors with concise docstrings referencing evaluation assumptions. + +## Testing Guidelines +- Favor pytest parameterization to cover candidate mutation edge cases. +- Place new tests alongside related modules under `tests/` using the `test_.py` pattern. +- Ensure evolution runs include deterministic seeds when feasible; capture fixtures for expensive evaluators. +- Add regression tests whenever logic affects scoring, database migrations, or patch synthesis. + +## Commit & Pull Request Guidelines +- Follow Conventional Commit prefixes observed in history (`feat:`, `fix:`, `docs:`); keep the subject under 72 chars. +- Squash noise commits locally; PRs should include a one-paragraph summary plus CLI or screenshot evidence for UI changes. +- Link tracking issues and note required credentials or configs in the PR body. +- Request review once CI (formatting + pytest) is green; highlight remaining risks or TODOs inline. + +## Configuration & Secrets +- Store API keys in `.env` (see `docs/getting_started.md`); never commit secrets or experiment artifacts. +- For multi-node or Slurm runs, duplicate configs into `configs/custom/` and document cluster dependencies in the PR. + +## Merging Upstream Updates (Syncing with SakanaAI/ShinkaEvolve) + +The Genesis repository is a fork/derivative of [SakanaAI/ShinkaEvolve](https://github.com/SakanaAI/ShinkaEvolve). We maintain a different package name (`genesis` instead of `shinka`) and custom features (E2B integration, etc.), which requires care when merging upstream updates. + +**Workflow:** + +1. **Configure Remote:** Ensure you have the upstream remote configured: + ```bash + git remote add upstream https://github.com/SakanaAI/ShinkaEvolve.git + ``` + +2. **Commit Local State:** **Crucial:** Ensure all your local changes are committed before merging. Git's rename detection relies on comparing the file content. If you have uncommitted changes (especially import renames), git might treat files as deleted/added instead of renamed, causing massive conflicts. + +3. **Fetch and Merge:** + ```bash + git fetch upstream + git merge upstream/main + ``` + * Git usually detects the `shinka/` -> `genesis/` folder rename automatically if the file contents are similar enough. + * If you see `CONFLICT (file location)`, git likely detected the rename but needs confirmation for new files. + +4. **Resolve Conflicts:** + * **New Upstream Files:** If upstream added a file in `shinka/new_file.py`, git might place it there. Move it to `genesis/new_file.py` using `git mv`. + * **Imports:** Upstream changes will re-introduce `from shinka...` imports. You must revert these to `from genesis...` in conflict resolution. + * **Config Defaults:** Be careful with `pyproject.toml` and `dbase.py`. Preserve our dependencies (e.g. `e2b`, `google-generativeai`) and config defaults (e.g. `db_path` optionality). + +5. **Update Dependencies:** + ```bash + python3 -m pip install . + ``` + +6. **Verify:** Run a quick evolution task (e.g., `mask_to_seg_example`) to ensure the system works with the updates. + +## Language Support + +Genesis primarily orchestrates the evolution process in Python, but it can optimize code in any language (e.g., Rust, C++, CUDA) provided you supply a suitable evaluator. + +### Optimizing Rust, C++, or Other Compiled Languages + +To optimize a compiled language like Rust: + +1. **Initial Program:** Create your `initial.rs` file. +2. **Evaluator Script:** Create a Python script (e.g., `evaluate.py`) that: + * Accepts the path to the candidate code (e.g., `main.rs`). + * Compiles the code (e.g., using `rustc` or `cargo build`). + * Runs the binary against test cases. + * Returns metrics (score, correctness) to Genesis via JSON or `run_genesis_eval`. +3. **Configuration:** Set `language: rust` in your task config. This ensures the LLM uses correct syntax highlighting and comments when generating patches. + +Example structure: +``` +examples/rust_task/ + initial.rs + evaluate.py # Wraps rustc and execution +``` diff --git a/docs/getting_started.md b/docs/getting_started.md index c235ec1..f04c30c 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -451,7 +451,7 @@ genesis_launch variant=my_variant verbose=true ### Getting Help -- Check the [examples](../examples/) directory for reference implementations +- Check the [examples](https://github.com/GeorgePearse/Genesis/tree/main/examples/) directory for reference implementations - See the [Configuration Guide](configuration.md) for detailed parameter explanations - Examine the generated experiment logs in the results directory diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d0623fb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,328 @@ +

+ + +
+ Genesis: Towards Open-Ended and Sample-Efficient Program Evolution 🧬
+

+ +

+ + + + +

+ +[`Genesis`](https://genesis.ai) is a framework that combines Large Language Models (LLMs) with evolutionary algorithms to drive scientific discovery. By leveraging the creative capabilities of LLMs and the optimization power of evolutionary search, `Genesis` enables automated exploration and improvement of scientific code. + +> **Note**: This implementation is based on and extends [Shinka AI](https://github.com/shinkadotai/shinka), an open-source platform for LLM-driven code evolution. We are grateful to the original authors for their foundational work. + +The system is inspired by the [AI Scientist](https://sakana.ai/ai-scientist/), [AlphaEvolve](https://deepmind.google/discover/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/), and [Darwin Goedel Machine](https://sakana.ai/dgm/). It maintains a population of programs that evolve over generations, with an ensemble of LLMs acting as intelligent mutation operators that suggest code improvements. + +The framework supports **parallel evaluation of candidates** locally, on a Slurm cluster, or in cloud sandboxes. It maintains an archive of successful solutions, enabling knowledge transfer between different evolutionary islands. `Genesis` is particularly well-suited for scientific tasks where there is a verifier available and the goal is to optimize performance metrics while maintaining code correctness and readability. + +![](conceptual.png) + +## Documentation 📝 + +| Guide | Description | What You'll Learn | +|-------|-------------|-------------------| +| 🚀 **[Getting Started](getting_started.md)** | Installation, basic usage, and examples | Setup, first evolution run, core concepts | +| 📓 **[Tutorial Notebook](https://github.com/GeorgePearse/Genesis/blob/main/examples/genesis_tutorial.ipynb)** | Interactive walkthrough of Genesis features | Hands-on examples, configuration, best practices | +| ⚙️ **[Configuration](configuration.md)** | Comprehensive configuration reference | All config options, optimization settings, advanced features | +| 🎨 **[WebUI](webui.md)** | Interactive visualization and monitoring | Real-time tracking, result analysis, debugging tools | +| 🗺️ **[Roadmap](roadmap.md)** | Future plans and language support | Supported languages, execution backends, planned features | + +## Installation & Quick Start 🚀 + +```bash +# Clone the repository +git clone https://github.com/GeorgePearse/Genesis +# Install uv if you haven't already +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Create environment and install Genesis +cd Genesis +uv venv --python 3.12 +source .venv/bin/activate # On Windows: .venv\Scripts\activate +uv pip install -e . + +# Run your first evolution experiment +genesis_launch variant=circle_packing_example +``` + +For detailed installation instructions and usage examples, see the [Getting Started Guide](getting_started.md). + +## Examples 📖 + +| Example | Description | Environment Setup | +|---------|-------------|-------------------| +| ⭕ [Circle Packing](https://github.com/GeorgePearse/Genesis/tree/main/examples/circle_packing) | Optimize circle packing to maximize radii. | `LocalJobConfig` | +| 🤖 [Agent Design](https://github.com/GeorgePearse/Genesis/tree/main/examples/adas_aime) | Design agent scaffolds for math tasks. | `LocalJobConfig` | +| 🎯 [ALE-Bench](https://github.com/GeorgePearse/Genesis/tree/main/examples/ale_bench) | Code optimization for ALE-Bench tasks. | `LocalJobConfig` | +| ✨ [Novelty Generator](https://github.com/GeorgePearse/Genesis/tree/main/examples/novelty_generator) | Generate creative, surprising outputs (e.g., ASCII art). | `LocalJobConfig` | + + +## `genesis` Run with Python API 🐍 + +For the simplest setup with default settings, you only need to specify the evaluation program: + +```python +from genesis.core import EvolutionRunner, EvolutionConfig +from genesis.database import DatabaseConfig +from genesis.launch import LocalJobConfig + +# Minimal config - only specify what's required +job_config = LocalJobConfig(eval_program_path="evaluate.py") +db_config = DatabaseConfig() +evo_config = EvolutionConfig(init_program_path="initial.py",) + +# Run evolution with defaults +runner = EvolutionRunner( + evo_config=evo_config, + job_config=job_config, + db_config=db_config, +) +runner.run() +``` + +
+EvolutionConfig Parameters (click to expand) + +| Key | Default Value | Type | Explanation | +|-----|---------------|------|-------------| +| `task_sys_msg` | `None` | `Optional[str]` | System message describing the optimization task | +| `patch_types` | `["diff"]` | `List[str]` | Types of patches to generate: "diff", "full", "cross" | +| `patch_type_probs` | `[1.0]` | `List[float]` | Probabilities for each patch type | +| `num_generations` | `10` | `int` | Number of evolution generations to run | +| `max_parallel_jobs` | `2` | `int` | Maximum number of parallel evaluation jobs | +| `max_patch_resamples` | `3` | `int` | Max times to resample a patch if it fails | +| `max_patch_attempts` | `5` | `int` | Max attempts to generate a valid patch | +| `job_type` | `"local"` | `str` | Job execution type: "local", "slurm_docker", "slurm_conda" | +| `language` | `"python"` | `str` | Programming language for evolution | +| `llm_models` | `["azure-gpt-4.1-mini"]` | `List[str]` | List of LLM models for code generation | +| `llm_dynamic_selection` | `None` | `Optional[Union[str, BanditBase]]` | Dynamic model selection strategy | +| `llm_dynamic_selection_kwargs` | `{}` | `dict` | Kwargs for dynamic selection | +| `llm_kwargs` | `{}` | `dict` | Additional kwargs for LLM calls | +| `meta_rec_interval` | `None` | `Optional[int]` | Interval for meta-recommendations | +| `meta_llm_models` | `None` | `Optional[List[str]]` | LLM models for meta-recommendations | +| `meta_llm_kwargs` | `{}` | `dict` | Kwargs for meta-recommendation LLMs | +| `meta_max_recommendations` | `5` | `int` | Max number of meta-recommendations | +| `embedding_model` | `None` | `Optional[str]` | Model for code embeddings | +| `init_program_path` | `"initial.py"` | `Optional[str]` | Path to initial program to evolve | +| `results_dir` | `None` | `Optional[str]` | Directory to save results (auto-generated if None) | +| `max_novelty_attempts` | `3` | `int` | Max attempts for novelty generation | +| `code_embed_sim_threshold` | `1.0` | `float` | Similarity threshold for code embeddings | +| `novelty_llm_models` | `None` | `Optional[List[str]]` | LLM models for novelty judgment | +| `novelty_llm_kwargs` | `{}` | `dict` | Kwargs for novelty LLMs | +| `use_text_feedback` | `False` | `bool` | Whether to use text feedback in evolution | + +
+ +
+DatabaseConfig Parameters (click to expand) + +| Key | Default Value | Type | Explanation | +|-----|---------------|------|-------------| +| `db_path` | `None` | `Optional[str]` | Database file path (auto-generated if None) | +| `num_islands` | `4` | `int` | Number of evolution islands for diversity | +| `archive_size` | `100` | `int` | Size of program archive per island | +| `elite_selection_ratio` | `0.3` | `float` | Proportion of elite programs for inspiration | +| `num_archive_inspirations` | `5` | `int` | Number of archive programs to use as inspiration | +| `num_top_k_inspirations` | `2` | `int` | Number of top-k programs for inspiration | +| `migration_interval` | `10` | `int` | Generations between island migrations | +| `migration_rate` | `0.1` | `float` | Proportion of island population to migrate | +| `island_elitism` | `True` | `bool` | Keep best programs on their original islands | +| `enforce_island_separation` | `True` | `bool` | Enforce full separation between islands | +| `parent_selection_strategy` | `"power_law"` | `str` | Parent selection: "weighted", "power_law", "beam_search" | +| `exploitation_alpha` | `1.0` | `float` | Power-law exponent (0=uniform, 1=power-law) | +| `exploitation_ratio` | `0.2` | `float` | Chance to pick parent from archive | +| `parent_selection_lambda` | `10.0` | `float` | Sharpness of sigmoid for weighted selection | +| `num_beams` | `5` | `int` | Number of beams for beam search selection | + +
+ +
+JobConfig Parameters (click to expand) + +**LocalJobConfig** (for local execution): +| Key | Default Value | Type | Explanation | +|-----|---------------|------|-------------| +| `eval_program_path` | `"evaluate.py"` | `Optional[str]` | Path to evaluation script | +| `extra_cmd_args` | `{}` | `Dict[str, Any]` | Additional command line arguments | +| `time` | `None` | `Optional[str]` | Time limit for job execution | +| `conda_env` | `None` | `Optional[str]` | Conda environment to run jobs in | + +**SlurmDockerJobConfig** (for SLURM with Docker): +| Key | Default Value | Type | Explanation | +|-----|---------------|------|-------------| +| `eval_program_path` | `"evaluate.py"` | `Optional[str]` | Path to evaluation script | +| `extra_cmd_args` | `{}` | `Dict[str, Any]` | Additional command line arguments | +| `image` | `"ubuntu:latest"` | `str` | Docker image to use | +| `image_tar_path` | `None` | `Optional[str]` | Path to Docker image tar file | +| `docker_flags` | `""` | `str` | Additional Docker flags | +| `partition` | `"gpu"` | `str` | SLURM partition to use | +| `time` | `"01:00:00"` | `str` | Job time limit | +| `cpus` | `1` | `int` | Number of CPUs to request | +| `gpus` | `1` | `int` | Number of GPUs to request | +| `mem` | `"8G"` | `Optional[str]` | Memory to request | + +**SlurmCondaJobConfig** (for SLURM with Conda): +| Key | Default Value | Type | Explanation | +|-----|---------------|------|-------------| +| `eval_program_path` | `"evaluate.py"` | `Optional[str]` | Path to evaluation script | +| `extra_cmd_args` | `{}` | `Dict[str, Any]` | Additional command line arguments | +| `conda_env` | `""` | `str` | Conda environment name | +| `modules` | `[]` | `Optional[List[str]]` | Environment modules to load | +| `partition` | `"gpu"` | `str` | SLURM partition to use | +| `time` | `"01:00:00"` | `str` | Job time limit | +| `cpus` | `1` | `int` | Number of CPUs to request | +| `gpus` | `1` | `int` | Number of GPUs to request | +| `mem` | `"8G"` | `Optional[str]` | Memory to request | + +
+ +### Evaluation Setup & Initial Solution 🏃 + +To use EvolutionRunner, you need two key files: The **`evaluate.py`** script defines how to test and score your programs - it runs multiple evaluations, validates results, and aggregates them into metrics that guide the `genesis` evolution loop. The **`initial.py`** file contains your starting solution with the core algorithm that will be iteratively improved by LLMs across generations. + + + + + + +
+ +**`evaluate.py` - Evaluation Script** + +```python +from genesis.core import run_genesis_eval + +def main(program_path: str, + results_dir: str): + metrics, correct, err = run_genesis_eval( + program_path=program_path, + results_dir=results_dir, + experiment_fn_name="run_experiment", + num_runs=3, # Multi-evals to aggreg. + get_experiment_kwargs=get_kwargs, + aggregate_metrics_fn=aggregate_fn, + validate_fn=validate_fn, # Optional + ) + +def get_kwargs(run_idx: int) -> dict: + return {"param1": "value", "param2": 42} + +def aggregate_fn(results: list) -> dict: + score = results[0] + text = results[1] + return { + "combined_score": float(score), + "public": {...}, # genesis-visible + "private": {...}, # genesis-invisible + "extra_data": {...}, # store as pkl + "text_feedback": text, # str fb + } + +if __name__ == "__main__": + # argparse program path & dir + main(program_path, results_dir) +``` + + + +**`initial.py` - Starting Solution** + +```python +# EVOLVE-BLOCK-START +def advanced_algo(): + # This will be evolved + return solution +# EVOLVE-BLOCK-END + +def run_experiment(**kwargs): + """Main called by evaluator""" + result = solve_problem(kwargs) + return result + +def solve_problem(params): + solution = advanced_algo() + return solution +``` + +**Key Points:** +- Eval name matches `experiment_fn_name` +- Use `EVOLVE-BLOCK-START` and `EVOLVE-BLOCK-END` to mark evolution sections +- Return format matches validation expectations +- Dependencies must be available in env +- Results can be unpacked for metrics +- Auto-stores several results in `results_dir` +- Can add text feedback in `genesis` loop +- Higher `combined_score` values indicate better performance (maximization) + +
+ + +## `genesis` Launcher with Hydra 🚀 + +`genesis` Launcher utilizes [Hydra](https://hydra.cc/) to configure and launch evolutionary experiments effortlessly. It supports concise configuration via Hydra's powerful override syntax, making it easy to manage and iterate scientific explorations. + +```bash +# Run with pre-configured variant +genesis_launch variant=circle_packing_example + +# Run with custom parameters +genesis_launch \ + task=circle_packing \ + database=island_large \ + evolution=small_budget \ + cluster=local \ + evo_config.num_generations=20 +``` + +For comprehensive configuration options and advanced usage, see the [Configuration Guide](configuration.md). + + +## Interactive WebUI 🎨 + +Monitor your evolution experiments in real-time with Genesis's interactive web interface! The WebUI provides live visualization of the evolutionary process, genealogy trees, and performance metrics. + +![WebUI Screenshot](webui.png) + +### Quick Start + +Launch the WebUI alongside your evolution experiment: + +```bash +# Start your evolution experiment +genesis_launch variant=circle_packing_example + +# In another terminal, launch the WebUI +genesis_visualize --port 8888 --open +``` + +For detailed WebUI documentation, see the [WebUI Guide](webui.md). + +## Related Open-Source Projects 🧑‍🔧 + +- **[Shinka AI](https://github.com/shinkadotai/shinka)**: The original implementation that Genesis is based on - a platform for LLM-driven program evolution +- [OpenEvolve](https://github.com/codelion/openevolve): An open-source implementation of AlphaEvolve +- [LLM4AD](https://github.com/Optima-CityU/llm4ad): A Platform for Algorithm Design with Large Language Model + +## Acknowledgments 🙏 + +Genesis is built upon the excellent work of the [Shinka AI](https://github.com/shinkadotai/shinka) project. We extend our gratitude to the original authors and contributors for creating such a robust foundation for LLM-driven code evolution. + +## Citation ✍️ + +If you use `Genesis` in your research, please cite: + +``` +@misc{genesis2025, + title={Genesis: Platform Experiments for LLM-Driven Program Evolution}, + author={Pearse, George}, + howpublished={\url{https://genesis.ai}}, + year={2025} +} +``` + +And please also consider citing the original Shinka AI work that this is based on. diff --git a/docs/papers.md b/docs/papers.md new file mode 100644 index 0000000..2af52fa --- /dev/null +++ b/docs/papers.md @@ -0,0 +1,385 @@ +# Recent Papers on LLM-Driven Code Evolution (2024-2025) + +This page surveys the rapidly evolving field of LLM-driven code evolution, genetic programming with language models, and automated program synthesis. These works form the theoretical and practical foundation for Genesis. + +--- + +## Overview + +The intersection of Large Language Models (LLMs) and evolutionary computation has emerged as one of the most active research areas in AI. LLMs provide creative code generation capabilities while evolutionary algorithms provide systematic search and optimization. Together, they enable automated discovery of novel algorithms and programs. + +Key themes in this research include: + +- **Sample Efficiency**: Reducing the number of LLM calls needed to find good solutions +- **Open-Ended Evolution**: Continuous improvement without predefined stopping criteria +- **Verifiable Discovery**: Ensuring evolved solutions are correct and novel +- **Multi-Language Support**: Evolving code beyond just Python + +--- + +## Foundational Systems + +### FunSearch (DeepMind, 2024) + +**Paper**: [Mathematical discoveries from program search with large language models](https://www.nature.com/articles/s41586-023-06924-6) (Nature, 2024) + +**GitHub**: [google-deepmind/funsearch](https://github.com/google-deepmind/funsearch) + +FunSearch (Function Search) pairs a pretrained LLM with an automated evaluator in an evolutionary loop. Key innovations: + +- **Program as Solution Representation**: Searches for programs that describe *how* to solve a problem, not just *what* the solution is +- **Island-Based Evolution**: Maintains diverse populations across islands to prevent premature convergence +- **No Fine-Tuning Required**: Works with API access to models like Codey or StarCoder + +**Key Results**: + +- First scientific discovery using an LLM: new solutions to the cap set problem (largest improvement in 20 years) +- Discovered more effective bin-packing algorithms with real-world applications +- Solutions are interpretable programs, not opaque neural outputs + +--- + +### AlphaEvolve (DeepMind, May 2025) + +**Paper**: [AlphaEvolve: A coding agent for scientific and algorithmic discovery](https://storage.googleapis.com/deepmind-media/DeepMind.com/Blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/AlphaEvolve.pdf) + +**Blog**: [deepmind.google/blog/alphaevolve](https://deepmind.google/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/) + +AlphaEvolve represents a major advancement over FunSearch, using Gemini 2.0 as the backbone LLM. Key improvements: + +| Feature | FunSearch | AlphaEvolve | +|---------|-----------|-------------| +| Code Scale | Single functions (10-20 lines) | Entire files (hundreds of lines) | +| Languages | Python only | Any programming language | +| Evaluation Time | <20 min on CPU | Hours on accelerators | +| Sample Efficiency | Millions of samples | Thousands of samples | + +**Key Results**: + +- **Matrix Multiplication**: Found algorithm for 4x4 complex matrices using 48 scalar multiplications (improving on Strassen's 1969 algorithm) +- **Google Infrastructure**: Heuristic deployed in Borg scheduler recovers 0.7% of worldwide compute resources +- **AI Training**: 23% speedup in kernel tiling, 32% speedup in FlashAttention operations +- Re-discovered SOTA for 75% of 50+ math problems, found improvements for 20% + +**Model Ensemble**: Uses Gemini 2.0 Flash (throughput) + Gemini 2.0 Pro (quality) for balanced exploration. + +--- + +### ShinkaEvolve (Sakana AI, September 2025) + +**Paper**: [ShinkaEvolve: Towards Open-Ended and Sample-Efficient Program Evolution](https://arxiv.org/abs/2509.19349) + +**GitHub**: [SakanaAI/ShinkaEvolve](https://github.com/SakanaAI/ShinkaEvolve) + +**Blog**: [sakana.ai/shinka-evolve](https://sakana.ai/shinka-evolve/) + +ShinkaEvolve ("Shinka" = evolution in Japanese) is the open-source framework that Genesis is forked from. It achieves remarkable sample efficiency through: + +1. **Adaptive Parent Sampling**: Balances exploration and exploitation dynamically +2. **Novelty-Based Rejection Filtering**: Avoids redundant evaluations +3. **Bandit-Based LLM Ensemble**: Dynamically selects best model for each mutation + +**Key Results**: + +| Benchmark | Result | Previous SOTA | +|-----------|--------|---------------| +| Circle Packing (n=26) | New SOTA in ~150 evaluations | Thousands of evaluations | +| AIME Math Reasoning | Evolved 3-stage architecture beats baselines | - | +| AtCoder (via ALE-Agent) | 2.3% mean improvement, one task 5th → 2nd | - | +| MoE Training Loss | Outperforms DeepSeek's "Global LBL" | - | + +**Real-World Victory**: Team Unagi won 1st place at ICFP 2025 Programming Contest using ShinkaEvolve to evolve their solver (up to 10x speedup). + +--- + +### Darwin Goedel Machine (Sakana AI, 2025) + +A self-improving AI system that can modify its own code to improve performance. + +**Key Results**: + +- Improved SWE-Bench score from 20% → 50% after 80 generations +- Improved Polyglot benchmark from 14.2% → 30.7% (best human-coded agent scores 16%) +- Strategies generalize across different foundation models and programming languages + +**Safety Finding**: The system sometimes attempted deceptive behavior (lying about running unit tests), highlighting the need for robust verification. + +--- + +### AI Scientist (Sakana AI, 2024-2025) + +**Paper v1**: [The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery](https://sakana.ai/ai-scientist/) + +**Paper v2**: [The AI Scientist-v2: Workshop-Level Automated Scientific Discovery](https://pub.sakana.ai/ai-scientist-v2/paper/paper.pdf) + +**GitHub**: [SakanaAI/AI-Scientist](https://github.com/SakanaAI/AI-Scientist) | [SakanaAI/AI-Scientist-v2](https://github.com/SakanaAI/AI-Scientist-v2) + +The AI Scientist automates the entire research lifecycle: + +1. **Ideation**: Brainstorms ideas, searches literature for novelty +2. **Experimentation**: Executes experiments, generates visualizations +3. **Paper Writing**: Produces LaTeX papers with automated citation +4. **Peer Review**: LLM-powered reviewer provides feedback + +**Key Results**: + +- v1: Produces papers judged as "Weak Accept" at top ML conferences (~$15/paper) +- v2: First fully AI-generated paper to exceed human acceptance threshold at ICLR workshop +- v2 uses Vision-Language Model feedback and eliminates need for human-authored templates + +--- + +## Evolutionary Program Synthesis + +### LLM_GP: LLM-Based Genetic Programming + +**Paper**: [Evolving Code with A Large Language Model](https://arxiv.org/abs/2401.07102) (GPEM, 2024) + +LLM_GP treats code as text and uses LLM prompts for evolutionary operators: + +- **Initialization**: LLM generates initial population from problem description +- **Selection**: Standard tournament/lexicase selection +- **Mutation/Crossover**: LLM rewrites code given parent(s) and fitness feedback + +Unlike traditional GP that manipulates syntax trees, LLM_GP operates on raw code text, enabling more flexible and semantically-aware variations. + +--- + +### SEIDR: Multi-Agent Program Synthesis + +**Paper**: [Fully Autonomous Programming Using Iterative Multi-Agent Debugging with Large Language Models](https://dl.acm.org/doi/10.1145/3719351) (ACM TELO) + +**GitHub**: [vadim0x60/seidr](https://github.com/vadim0x60/seidr) + +SEIDR (Synthesize, Execute, Instruct, Debug, Rank) addresses the "near-miss syndrome" where LLM-generated code is almost correct: + +1. **Synthesize**: Generate candidate solutions +2. **Execute**: Run against test cases, assign fitness +3. **Instruct**: Analyze failures, generate debugging prompts +4. **Debug**: Repair failed solutions +5. **Rank**: Select best candidates using lexicase/tournament selection + +**Key Results**: + +- 19/25 problems solved on PSB2 with <1000 program executions +- Outperforms both Codex-only and traditional GP approaches +- Benefits from using multiple LLMs (introduces more variation) + +--- + +### EvoPrompting: Neural Architecture Search + +**Paper**: [EvoPrompting: Language Models for Code-Level Neural Architecture Search](https://arxiv.org/abs/2302.14838) (NeurIPS 2023) + +Uses LLMs as adaptive mutation/crossover operators for neural architecture search: + +- Replaces traditional NAS search space with LLM vocabulary +- Combines evolutionary prompt engineering with soft prompt-tuning +- LLM improves round-over-round through adaptation + +**Key Results**: + +- Novel CNN architectures outperforming human designs on MNIST-1D +- SOTA on 21/30 tasks in CLRS Algorithmic Reasoning Benchmark + +--- + +### Many-Objective Grammar-Guided GP (MaOG3P) + +**Paper**: [Enhancing Program Synthesis with Large Language Models Using Many-Objective Grammar-Guided Genetic Programming](https://www.mdpi.com/1999-4893/17/7/287) (Algorithms, 2024) + +Combines LLMs with grammar-guided GP: + +1. LLM generates initial code from task description +2. Code is mapped to BNF grammar-compliant program +3. Grammar-guided GP evolves with similarity to LLM solution as secondary objective + +Addresses LLM struggles with complex syntax while leveraging their semantic understanding. + +--- + +### Genetic Improvement of LLM-Generated Code + +**Paper**: [Enhancing Large Language Models-Based Code Generation by Leveraging Genetic Improvement](https://link.springer.com/chapter/10.1007/978-3-031-56957-9_7) (EuroGP 2024) + +Uses evolutionary Genetic Improvement to refine LLM-generated code using test cases. Demonstrates that combining LLMs with evolutionary post-processing yields better results than either alone. + +--- + +## Self-Improving Systems + +### SICA: Self-Improving Coding Agent (ICLR 2025) + +**Paper**: [A Self-Improving Coding Agent](https://openreview.net/pdf?id=rShJCyLsOr) (ICLR 2025 Workshop) + +An LLM coding agent that autonomously edits its own codebase to improve performance: + +- **Meta Agent Loop**: Alternates between benchmarking and self-modification +- **Performance Gains**: 17% → 53% improvement on SWE-Bench Verified subset +- **Generalization**: Also improves on LiveCodeBench and synthetic benchmarks + +Key insight: Self-improvement works especially well for "agentic" tasks where the base LLM benefits from additional structure and guidance. + +--- + +## Surveys and Reviews + +### Evolutionary Computation in the Era of Large Language Models + +**Paper**: [Survey and Roadmap](https://arxiv.org/abs/2401.10034) (IEEE TEVC, 2024) + +**GitHub**: [wuxingyu-ai/LLM4EC](https://github.com/wuxingyu-ai/LLM4EC) + +Comprehensive survey covering three research directions: + +1. **LLM-Enhanced EA**: Using LLMs as evolution operators, leveraging domain knowledge +2. **EA-Enhanced LLM**: Using EAs for prompt optimization and neural architecture search +3. **Synergistic Applications**: Code generation, software engineering, text generation + +Essential reading for understanding the full landscape of LLM+EA research. + +--- + +### When Large Language Models Meet Evolutionary Algorithms + +**Paper**: [Potential Enhancements and Challenges](https://spj.science.org/doi/10.34133/research.0646) (Research, 2024) + +Explores how LLMs can enhance EAs and vice versa, with discussion of challenges including: + +- Computational costs +- Evaluation reliability +- Benchmark contamination + +--- + +## Benchmarks + +### SWE-bench + +**Paper**: [SWE-bench: Can Language Models Resolve Real-world Github Issues?](https://github.com/SWE-bench/SWE-bench) (ICLR 2024 Oral) + +2,200+ real GitHub issues from 12 Python repositories. Models must generate patches to fix issues. + +**2024-2025 Progress**: + +| System | SWE-bench Verified | +|--------|-------------------| +| Claude 3.5 Sonnet | 49% | +| CodeStory Midwit Agent | 62% | +| Gemini 2.5 Pro | 63.8% | +| OpenAI o3 | 72% (reported) | + +**Caveats**: Research found 32.67% of patches involve "solution leakage" (answer in issue description). + +--- + +### LiveCodeBench + +Contamination-free benchmark using problems from weekly coding contests (LeetCode, AtCoder, CodeForces) with release date tagging. + +--- + +### CodeElo (2025) + +Elo rating system for LLM code generation using Codeforces problems, similar to chess rankings. + +--- + +## Open-Source Implementations + +### OpenEvolve + +**GitHub**: [codelion/openevolve](https://github.com/codelion/openevolve) | [algorithmicsuperintelligence/openevolve](https://github.com/algorithmicsuperintelligence/openevolve) + +**PyPI**: `pip install openevolve` + +Open-source implementation of AlphaEvolve. Features: + +- Codebase-scale optimization (not just single functions) +- Multi-LLM support via LiteLLM +- Replicates AlphaEvolve circle packing results +- HotpotQA prompt optimization example (+23% accuracy) + +--- + +### OptiLLM + +**GitHub**: [codelion/optillm](https://github.com/codelion/optillm) + +OpenAI API-compatible proxy implementing inference optimization strategies: + +- **Prompt Optimization**: Few-shot learning, structured prompts +- **Model Selection**: Task-specific model routing +- **Inference Optimization**: Quantization, hardware acceleration +- **Decoding Techniques**: CoT decoding, entropy-based decoding +- **Mixture of Agents (MoA)**: Ensemble multiple models + +Drop-in replacement for OpenAI API with automatic optimization. + +--- + +### ShinkaEvolve + +**GitHub**: [SakanaAI/ShinkaEvolve](https://github.com/SakanaAI/ShinkaEvolve) + +**License**: Apache-2.0 + +The upstream project Genesis is forked from. Features WebUI, examples, and multi-backend support. See the main Genesis documentation for usage. + +--- + +### FunSearch + +**GitHub**: [google-deepmind/funsearch](https://github.com/google-deepmind/funsearch) + +Reference implementation of the FunSearch algorithm. + +--- + +### AI Scientist + +**GitHub**: [SakanaAI/AI-Scientist](https://github.com/SakanaAI/AI-Scientist) + +Full pipeline for automated scientific research. + +--- + +## Further Reading + +### Resource Collections + +- **LLM4EC**: [GitHub - wuxingyu-ai/LLM4EC](https://github.com/wuxingyu-ai/LLM4EC) - Curated papers at the intersection of LLMs and evolutionary computation +- **Papers With Code - Program Synthesis**: [paperswithcode.com/task/program-synthesis](https://paperswithcode.com/task/program-synthesis/latest) + +### Related Topics + +- **Automated Machine Learning (AutoML)**: Neural architecture search, hyperparameter optimization +- **Neuroevolution**: Evolving neural network weights and architectures +- **Program Repair**: Automated bug fixing using LLMs +- **Code Generation Benchmarks**: HumanEval, MBPP, CodeContests + +--- + +## Citation + +If you use Genesis in your research, please cite: + +```bibtex +@software{genesis2025, + title = {Genesis: LLM-Driven Program Evolution}, + author = {Pearse, George}, + year = {2025}, + url = {https://github.com/GeorgePearse/Genesis} +} +``` + +For the underlying ShinkaEvolve framework: + +```bibtex +@article{shinkaevolve2025, + title = {ShinkaEvolve: Towards Open-Ended and Sample-Efficient Program Evolution}, + author = {Sakana AI}, + year = {2025}, + journal = {arXiv preprint arXiv:2509.19349} +} +``` diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..ed8094f --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,135 @@ +# Genesis Roadmap + +## Vision + +Genesis aims to be the universal framework for LLM-driven code evolution across any programming language, execution environment, and optimization objective. + +--- + +## Current Language Support + +| Language | Local | Slurm | E2B (base) | E2B (custom) | Notes | +|----------|:-----:|:-----:|:----------:|:------------:|-------| +| **Python** | ✅ | ✅ | ✅ | ✅ | First-class support, all features | +| **Rust** | ✅ | ✅ | ❌ | ✅ | Needs `rustc` in environment | +| **C++** | ✅ | ✅ | ❌ | ✅ | Needs `g++` or `clang++` | +| **CUDA** | ⚠️ | ✅ | ❌ | ❌ | Requires GPU + `nvcc` | +| **JavaScript/TypeScript** | ✅ | ✅ | ✅ | ✅ | Node.js available in E2B base | + +### Adding New Language Support + +To evolve code in any language, you need: + +1. **Compiler/Interpreter** in the execution environment +2. **Python evaluation wrapper** that: + - Compiles the evolved code (if needed) + - Runs it against test cases + - Returns a numeric fitness score + +See `examples/mask_to_seg_rust/` for a complete Rust example. + +--- + +## Execution Backend Status + +| Backend | Status | Parallelism | GPU Support | Best For | +|---------|:------:|:-----------:|:-----------:|----------| +| **Local** | ✅ Done | 1-4 jobs | If available | Development, testing | +| **Slurm (Docker)** | ✅ Done | Unlimited | ✅ Yes | HPC clusters | +| **Slurm (Conda)** | ✅ Done | Unlimited | ✅ Yes | HPC clusters | +| **E2B** | ✅ Done | ~50 jobs | ❌ No | Cloud parallel execution | +| **Modal** | 🔜 Planned | Unlimited | ✅ Yes | Serverless GPU | +| **Ray** | 💭 Idea | Unlimited | ✅ Yes | Distributed clusters | + +--- + +## Planned Improvements + +### High Priority + +#### E2B Templates for Compiled Languages +- [ ] Pre-built E2B templates with common toolchains: + - `genesis-rust` - Rust toolchain (rustc, cargo) + - `genesis-cpp` - C++ toolchain (g++, clang++, cmake) + - `genesis-go` - Go toolchain + - `genesis-full` - All languages combined +- [ ] One-command template deployment +- [ ] Documentation for custom template creation + +#### Modal Integration for GPU Code +- [ ] CUDA kernel evolution with GPU execution +- [ ] PyTorch/JAX model optimization +- [ ] Serverless GPU with automatic scaling +- [ ] Cost tracking and budget limits + +#### Improved Rust Support +- [ ] Cargo project support (not just single-file rustc) +- [ ] Crate dependency management +- [ ] SIMD-aware optimization prompts +- [ ] Benchmark-driven fitness (criterion.rs integration) + +### Medium Priority + +#### Language-Specific LLM Optimization Hints +- [ ] **Rust**: Ownership/borrowing patterns, SIMD intrinsics, zero-copy +- [ ] **C++**: Template metaprogramming, cache optimization, vectorization +- [ ] **CUDA**: Warp efficiency, shared memory, occupancy optimization +- [ ] **Python**: NumPy vectorization, Cython hints, memory views + +#### Multi-Language Project Evolution +- [ ] Python + Rust (PyO3 bindings) +- [ ] Python + C++ (pybind11) +- [ ] Evolve both sides of FFI boundaries +- [ ] Cross-language fitness evaluation + +#### Enhanced Parallelism +- [ ] Adaptive `max_parallel_jobs` based on backend capacity +- [ ] Job priority queuing +- [ ] Preemption for higher-fitness candidates +- [ ] Distributed island model across backends + +### Future Exploration + +#### Additional Languages +| Language | Use Case | Complexity | +|----------|----------|------------| +| **WebAssembly** | Browser-based evolution, portable binaries | Medium | +| **Go** | Systems programming, microservices | Easy | +| **Julia** | Scientific computing, differentiable programming | Medium | +| **Zig** | Systems programming with safety | Medium | +| **Mojo** | Python syntax + systems performance | Hard (new language) | +| **Haskell** | Functional algorithm optimization | Medium | + +#### Advanced Features +- [ ] **Auto-vectorization verification** - Ensure SIMD is actually used +- [ ] **Formal verification integration** - Prove evolved code correctness +- [ ] **Energy-aware optimization** - Minimize power consumption +- [ ] **Hardware-specific tuning** - ARM vs x86, specific CPU features + +--- + +## Completed Milestones + +- [x] Core evolution framework with island model +- [x] Local execution backend +- [x] Slurm cluster support (Docker + Conda) +- [x] E2B cloud sandbox integration +- [x] Python language support +- [x] Rust language support (single-file) +- [x] WebUI for experiment monitoring +- [x] Novelty search and diversity maintenance +- [x] Multi-LLM support (OpenAI, Anthropic, Google, DeepSeek) +- [x] Hydra configuration system + +--- + +## Contributing + +We welcome contributions! Priority areas: + +1. **E2B templates** for compiled languages +2. **Modal backend** implementation +3. **Language-specific examples** and evaluators +4. **Documentation** improvements + +See [developer_guide.md](developer_guide.md) for contribution guidelines. diff --git a/examples/circle_packing/evaluate_standalone.py b/examples/circle_packing/evaluate_standalone.py new file mode 100644 index 0000000..a8f90ff --- /dev/null +++ b/examples/circle_packing/evaluate_standalone.py @@ -0,0 +1,186 @@ +""" +Standalone evaluator for circle packing (works in E2B without genesis package). + +This evaluator can run in isolated environments like E2B sandboxes +without requiring the full genesis package to be installed. +""" + +import os +import sys +import json +import argparse +import importlib.util +import traceback +import numpy as np +from typing import Tuple, Optional, Dict, Any, List + + +def load_module_from_path(path: str): + """Dynamically load a Python module from a file path.""" + spec = importlib.util.spec_from_file_location("program", path) + if spec is None or spec.loader is None: + raise ImportError(f"Cannot load module from {path}") + module = importlib.util.module_from_spec(spec) + sys.modules["program"] = module + spec.loader.exec_module(module) + return module + + +def validate_packing( + centers: np.ndarray, + radii: np.ndarray, + reported_sum: float, + atol: float = 1e-6, +) -> Tuple[bool, Optional[str]]: + """ + Validates circle packing results. + + Args: + centers: np.array of shape (n, 2) with (x, y) coordinates + radii: np.array of shape (n,) with radius of each circle + reported_sum: The reported sum of radii + + Returns: + (is_valid: bool, error_message: Optional[str]) + """ + if not isinstance(centers, np.ndarray): + centers = np.array(centers) + if not isinstance(radii, np.ndarray): + radii = np.array(radii) + + n_expected = 26 + + # Check shapes + if centers.shape != (n_expected, 2): + return False, f"Centers shape incorrect. Expected ({n_expected}, 2), got {centers.shape}" + if radii.shape != (n_expected,): + return False, f"Radii shape incorrect. Expected ({n_expected},), got {radii.shape}" + + # Check no negative radii + if np.any(radii < 0): + negative_indices = np.where(radii < 0)[0] + return False, f"Negative radii found for circles at indices: {negative_indices}" + + # Check sum matches reported + if not np.isclose(np.sum(radii), reported_sum, atol=atol): + return False, f"Sum of radii ({np.sum(radii):.6f}) does not match reported ({reported_sum:.6f})" + + # Check all circles inside unit square + for i in range(n_expected): + x, y = centers[i] + r = radii[i] + is_outside = ( + x - r < -atol or x + r > 1 + atol or y - r < -atol or y + r > 1 + atol + ) + if is_outside: + return False, f"Circle {i} (x={x:.4f}, y={y:.4f}, r={r:.4f}) is outside unit square." + + # Check no overlaps + for i in range(n_expected): + for j in range(i + 1, n_expected): + dist = np.sqrt(np.sum((centers[i] - centers[j]) ** 2)) + if dist < radii[i] + radii[j] - atol: + return False, f"Circles {i} & {j} overlap. Dist: {dist:.4f}, Sum Radii: {(radii[i] + radii[j]):.4f}" + + return True, "All validations passed" + + +def main(program_path: str, results_dir: str): + """Run circle packing evaluation.""" + print(f"Evaluating program: {program_path}") + print(f"Saving results to: {results_dir}") + os.makedirs(results_dir, exist_ok=True) + + metrics = {"combined_score": 0.0, "error": None} + correct_result = {"correct": False, "error": None} + + try: + # Load the program module + module = load_module_from_path(program_path) + + # Check for run_packing function + if not hasattr(module, "run_packing"): + raise AttributeError("Program must have a 'run_packing' function") + + # Run the packing algorithm + result = module.run_packing() + + if len(result) == 3: + centers, radii, sum_radii = result + elif len(result) == 2: + centers, radii = result + sum_radii = np.sum(radii) + else: + raise ValueError(f"run_packing returned {len(result)} values, expected 2 or 3") + + # Validate the result + is_valid, error_msg = validate_packing(centers, radii, sum_radii) + + if is_valid: + # Format centers for display + centers_str = "\n".join( + [f" centers[{i}] = ({x:.4f}, {y:.4f})" + for i, (x, y) in enumerate(centers)] + ) + + metrics = { + "combined_score": float(sum_radii), + "public": { + "centers_str": centers_str, + "num_circles": int(centers.shape[0]), + }, + "private": { + "reported_sum_of_radii": float(sum_radii), + }, + } + correct_result = {"correct": True, "error": None} + print(f"SUCCESS: Sum of radii = {sum_radii:.6f}") + else: + metrics = {"combined_score": 0.0, "error": error_msg} + correct_result = {"correct": False, "error": error_msg} + print(f"VALIDATION FAILED: {error_msg}") + + # Save extra data + try: + extra_file = os.path.join(results_dir, "extra.npz") + np.savez(extra_file, centers=centers, radii=radii, reported_sum=sum_radii) + print(f"Saved extra data to {extra_file}") + except Exception as e: + print(f"Warning: Could not save extra.npz: {e}") + + except Exception as e: + error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}" + metrics = {"combined_score": 0.0, "error": error_msg} + correct_result = {"correct": False, "error": error_msg} + print(f"EXECUTION ERROR: {e}") + traceback.print_exc() + + # Save results + metrics_file = os.path.join(results_dir, "metrics.json") + correct_file = os.path.join(results_dir, "correct.json") + + with open(metrics_file, "w") as f: + json.dump(metrics, f, indent=2) + print(f"Saved metrics to {metrics_file}") + + with open(correct_file, "w") as f: + json.dump(correct_result, f, indent=2) + print(f"Saved correct status to {correct_file}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Circle packing evaluator (standalone)") + parser.add_argument( + "--program_path", + type=str, + default="main.py", + help="Path to program to evaluate (must contain 'run_packing')", + ) + parser.add_argument( + "--results_dir", + type=str, + default="results", + help="Directory to save results", + ) + args = parser.parse_args() + main(args.program_path, args.results_dir) diff --git a/examples/e2b_demo/README.md b/examples/e2b_demo/README.md new file mode 100644 index 0000000..be29a2d --- /dev/null +++ b/examples/e2b_demo/README.md @@ -0,0 +1,185 @@ +# E2B Backend for Genesis + +This directory demonstrates using [E2B](https://e2b.dev) cloud sandboxes as the execution backend for Genesis experiments. + +## What is E2B? + +E2B provides isolated, ephemeral cloud sandboxes for code execution. Each sandbox is a fresh Linux environment that: +- Runs untrusted code safely +- Has no interference with other sandboxes +- Automatically cleans up after timeout +- Supports parallel execution (10+ simultaneous sandboxes) + +## Quick Start + +```bash +# 1. Install E2B +pip install e2b + +# 2. Set your API key (get from https://e2b.dev) +export E2B_API_KEY=your_api_key + +# 3. Run the demo +python demo_e2b_backend.py +``` + +## Three Ways to Use E2B with Genesis + +### 1. CLI with Hydra (Simplest) + +```bash +# Use existing E2B variant +genesis_launch +variant=squeeze_hnsw_e2b + +# Or override cluster config +genesis_launch +task=circle_packing +cluster=e2b + +# Full customization +genesis_launch \ + +task=circle_packing \ + +cluster=e2b \ + job_config.timeout=300 \ + job_config.dependencies="[numpy,scipy]" \ + evo_config.max_parallel_jobs=10 +``` + +### 2. Python API + +```python +from genesis.core import EvolutionRunner, EvolutionConfig +from genesis.launch import E2BJobConfig +from genesis.database import DatabaseConfig + +runner = EvolutionRunner( + evo_config=EvolutionConfig( + job_type="e2b", + num_generations=100, + max_parallel_jobs=10, + ), + job_config=E2BJobConfig( + eval_program_path="evaluate.py", + template="base", + timeout=300, + dependencies=["numpy", "scipy"], + ), + db_config=DatabaseConfig(num_islands=4), + task_name="my_optimization", +) + +best_program, best_score = runner.run() +``` + +### 3. Direct E2B Module + +```python +from genesis.launch.e2b import ( + submit_with_files, + download_results, + cleanup_sandbox, +) + +job_id = submit_with_files( + log_dir="./results", + exec_fname="program.py", + eval_program_path="evaluate.py", + timeout=120, + template="base", + dependencies=["numpy"], +) + +download_results(job_id) +cleanup_sandbox(job_id) +``` + +## E2B Configuration Options + +| Option | Description | Default | +|--------|-------------|---------| +| `template` | E2B sandbox template | `"base"` | +| `timeout` | Max execution time (seconds) | `300` | +| `dependencies` | pip packages to install | `[]` | +| `additional_files` | Extra files to upload | `{}` | +| `env_vars` | Environment variables | `{}` | + +## How It Works + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Genesis Runner │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Sample │ │ Mutate │ │ Submit │ │ +│ │ Parent │──│ (LLM) │──│ to E2B │ │ +│ └─────────────┘ └─────────────┘ └──────┬──────┘ │ +└────────────────────────────────────────────┼───────────────┘ + │ + ┌────────────────────────┼────────────────┐ + │ E2B Cloud │ │ + │ ┌────────────────────▼──────────┐ │ + │ │ Sandbox 1 │ │ + │ │ ┌──────────┐ ┌──────────┐ │ │ + │ │ │ main.py │ │evaluate.py│ │ │ + │ │ └────┬─────┘ └─────┬────┘ │ │ + │ │ └───────┬──────┘ │ │ + │ │ ▼ │ │ + │ │ metrics.json │ │ + │ │ correct.json │ │ + │ └──────────────────────────────┘ │ + │ │ + │ ┌──────────────────────────────┐ │ + │ │ Sandbox 2 │ │ + │ │ (parallel) │ │ + │ └──────────────────────────────┘ │ + │ ... │ + └────────────────────────────────────────┘ +``` + +## Files in This Demo + +- `demo_e2b_backend.py` - Comprehensive demo of all E2B integration methods +- `run_circle_packing_e2b.py` - Run circle packing with E2B backend +- `README.md` - This file + +## Configuration Files + +- `configs/cluster/e2b.yaml` - Base E2B cluster config +- `configs/variant/squeeze_hnsw_e2b.yaml` - Example E2B variant + +## Benefits of E2B Backend + +| Feature | Local | SLURM | E2B | +|---------|-------|-------|-----| +| Isolation | ❌ | ✅ | ✅ | +| Parallel | Limited | ✅ | ✅ | +| Setup | None | Complex | Simple | +| Cost | Free | HPC access | Pay-per-use | +| Cleanup | Manual | Manual | Automatic | +| Safe for untrusted code | ❌ | ✅ | ✅ | + +## Troubleshooting + +**"E2B_API_KEY not set"** +```bash +export E2B_API_KEY=your_api_key +``` + +**"E2B package not installed"** +```bash +pip install e2b +``` + +**"Sandbox timeout"** +Increase timeout in config: +```yaml +job_config: + timeout: 600 # 10 minutes +``` + +**"Dependencies missing in sandbox"** +Add to dependencies list: +```yaml +job_config: + dependencies: + - numpy + - scipy + - your-package +``` diff --git a/examples/e2b_demo/demo_e2b_backend.py b/examples/e2b_demo/demo_e2b_backend.py new file mode 100644 index 0000000..22d8984 --- /dev/null +++ b/examples/e2b_demo/demo_e2b_backend.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python3 +""" +Demonstration: Using E2B as the Backend for Genesis + +This script shows how to use E2B cloud sandboxes as the execution backend +for Genesis experiments. E2B provides isolated, ephemeral environments +that enable highly parallel code evaluation. + +There are THREE ways to use E2B with Genesis: +1. Direct E2B module usage (this demo) +2. Via the JobScheduler abstraction +3. Via Hydra config (CLI: genesis_launch +variant=_e2b) + +Prerequisites: + pip install e2b + export E2B_API_KEY=your_api_key # Get from https://e2b.dev +""" + +import os +import sys +import json +import tempfile +from pathlib import Path + +# Add Genesis to path if running from examples directory +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + + +def demo_1_direct_e2b_usage(): + """ + Demo 1: Direct E2B Module Usage + + Shows how to use the low-level E2B functions to submit and monitor jobs. + Useful for custom integrations or one-off evaluations. + """ + print("\n" + "=" * 60) + print("Demo 1: Direct E2B Module Usage") + print("=" * 60) + + from genesis.launch.e2b import ( + submit_with_files, + get_job_status, + download_results, + cleanup_sandbox, + get_sandbox_info, + ) + + # Create a simple program to evaluate + program_code = ''' +"""Simple optimization function for E2B demo""" +import numpy as np + +def optimize(): + """Return the best solution found.""" + # Simple optimization: find maximum of a quadratic + x = np.linspace(-10, 10, 100) + y = -(x - 2.5)**2 + 10 # Maximum at x=2.5, y=10 + best_idx = np.argmax(y) + return {"x": float(x[best_idx]), "y": float(y[best_idx])} + +def run_experiment(): + """Entry point for Genesis evaluation.""" + result = optimize() + return result +''' + + evaluator_code = ''' +"""Evaluator for E2B demo""" +import os +import sys +import json +import argparse +import importlib.util + +def load_module(path): + """Dynamically load a Python module.""" + spec = importlib.util.spec_from_file_location("program", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + +def main(program_path, results_dir): + os.makedirs(results_dir, exist_ok=True) + + try: + # Load and run the program + module = load_module(program_path) + result = module.run_experiment() + + # Save metrics + metrics = { + "combined_score": result["y"], + "public": {"best_x": result["x"], "best_y": result["y"]}, + "private": {} + } + + with open(os.path.join(results_dir, "metrics.json"), "w") as f: + json.dump(metrics, f, indent=2) + + with open(os.path.join(results_dir, "correct.json"), "w") as f: + json.dump({"correct": True, "error": None}, f, indent=2) + + print(f"SUCCESS: Best y = {result['y']:.4f} at x = {result['x']:.4f}") + + except Exception as e: + with open(os.path.join(results_dir, "metrics.json"), "w") as f: + json.dump({"combined_score": 0.0, "error": str(e)}, f) + with open(os.path.join(results_dir, "correct.json"), "w") as f: + json.dump({"correct": False, "error": str(e)}, f) + print(f"FAILED: {e}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--program_path", default="main.py") + parser.add_argument("--results_dir", default="results") + args = parser.parse_args() + main(args.program_path, args.results_dir) +''' + + # Create temporary files + with tempfile.TemporaryDirectory() as tmpdir: + program_path = Path(tmpdir) / "program.py" + eval_path = Path(tmpdir) / "evaluate.py" + results_dir = Path(tmpdir) / "results" + + program_path.write_text(program_code) + eval_path.write_text(evaluator_code) + results_dir.mkdir() + + print(f"\nProgram: {program_path}") + print(f"Evaluator: {eval_path}") + print(f"Results: {results_dir}") + + # Submit to E2B + print("\n>>> Submitting job to E2B sandbox...") + job_id = submit_with_files( + log_dir=str(results_dir), + exec_fname=str(program_path), + eval_program_path=str(eval_path), + timeout=60, + template="base", + verbose=True, + dependencies=["numpy"], + ) + print(f"Job ID: {job_id}") + + # Check status + status = get_job_status(job_id) + print(f"Status: {'running' if status else 'completed'}") + + # Get sandbox info + info = get_sandbox_info(job_id) + print(f"Sandbox info: {json.dumps(info, indent=2, default=str)}") + + # Download results + print("\n>>> Downloading results...") + success = download_results(job_id, verbose=True) + print(f"Download successful: {success}") + + # Read results + metrics_file = results_dir / "metrics.json" + if metrics_file.exists(): + with open(metrics_file) as f: + metrics = json.load(f) + print(f"\nResults: {json.dumps(metrics, indent=2)}") + + # Cleanup + cleanup_sandbox(job_id, verbose=True) + print("\nSandbox cleaned up.") + + +def demo_2_job_scheduler(): + """ + Demo 2: Using the JobScheduler Abstraction + + The JobScheduler provides a unified interface for all backends + (local, SLURM, E2B). This is the recommended approach for + production use. + """ + print("\n" + "=" * 60) + print("Demo 2: Using JobScheduler with E2B Backend") + print("=" * 60) + + from genesis.launch import JobScheduler, E2BJobConfig + + # Configure E2B backend + config = E2BJobConfig( + eval_program_path="evaluate.py", # Will be resolved relative to task + template="base", + timeout=120, + dependencies=["numpy", "scipy"], + additional_files={}, + env_vars={"PYTHONUNBUFFERED": "1"}, + ) + + print("\nE2B Job Configuration:") + print(f" Template: {config.template}") + print(f" Timeout: {config.timeout}s") + print(f" Dependencies: {config.dependencies}") + + # Create scheduler (job_type="e2b" selects E2B backend) + scheduler = JobScheduler( + job_type="e2b", + config=config, + verbose=True, + ) + + print(f"\nScheduler created with job_type='e2b'") + print("The scheduler provides these methods:") + print(" - submit_async(exec_fname, results_dir) -> job_id") + print(" - check_job_status(job) -> bool (True if running)") + print(" - get_job_results(job_id, results_dir) -> Dict") + print(" - run(exec_fname, results_dir) -> Tuple[Dict, float] (sync)") + + +def demo_3_hydra_config(): + """ + Demo 3: Using Hydra Configuration (CLI) + + Shows how to launch E2B experiments using Hydra config composition. + This is the simplest way to use E2B with Genesis. + """ + print("\n" + "=" * 60) + print("Demo 3: Hydra Configuration for E2B") + print("=" * 60) + + print(""" +To run Genesis with E2B via CLI: + + # Option 1: Use an existing E2B variant + genesis_launch +variant=squeeze_hnsw_e2b + + # Option 2: Override cluster config to use E2B + genesis_launch +task=circle_packing +cluster=e2b + + # Option 3: Full customization + genesis_launch \\ + +task=circle_packing \\ + +cluster=e2b \\ + job_config.timeout=300 \\ + job_config.dependencies="[numpy,scipy]" \\ + evo_config.max_parallel_jobs=10 + +Configuration files: + configs/cluster/e2b.yaml - Base E2B config + configs/variant/*_e2b.yaml - Task-specific E2B variants + +Key E2B settings in config: + job_config: + _target_: genesis.launch.E2BJobConfig + template: "base" # E2B sandbox template + timeout: 300 # Sandbox timeout (seconds) + dependencies: # pip packages to install + - numpy + - scipy + additional_files: {} # Extra files to upload + env_vars: {} # Environment variables + + evo_config: + job_type: "e2b" # Select E2B backend + max_parallel_jobs: 10 # Parallel sandbox limit +""") + + +def demo_4_full_evolution(): + """ + Demo 4: Full Evolution Run with E2B + + Shows how to programmatically run a complete evolution + using E2B as the backend. + """ + print("\n" + "=" * 60) + print("Demo 4: Full Evolution with E2B Backend") + print("=" * 60) + + print(""" +Example code for running a full evolution with E2B: + +```python +from genesis.core import EvolutionRunner, EvolutionConfig +from genesis.launch import E2BJobConfig, JobScheduler +from genesis.database import DatabaseConfig + +# Configure E2B job execution +job_config = E2BJobConfig( + eval_program_path="evaluate.py", + template="base", + timeout=300, + dependencies=["numpy", "scipy"], +) + +# Configure database (multi-island model) +db_config = DatabaseConfig( + num_islands=4, + archive_size=50, + db_type="sqlite", +) + +# Configure evolution parameters +evo_config = EvolutionConfig( + job_type="e2b", # Use E2B backend + num_generations=100, + max_parallel_jobs=10, # 10 sandboxes in parallel + temperature=0.8, + initial_code_path="initial.py", +) + +# Create and run the evolution +runner = EvolutionRunner( + evo_config=evo_config, + job_config=job_config, + db_config=db_config, + task_name="my_optimization", +) + +# This runs the evolutionary loop +best_program, best_score = runner.run() + +print(f"Best score: {best_score}") +print(f"Best code:\\n{best_program.code}") +``` +""") + + +def demo_5_e2b_with_mcp(): + """ + Demo 5: E2B via MCP Server + + Shows how to use E2B through the Genesis MCP server, + which can be called from Claude Code or other MCP clients. + """ + print("\n" + "=" * 60) + print("Demo 5: E2B via Genesis MCP Server") + print("=" * 60) + + print(""" +The Genesis MCP server exposes E2B functionality: + +1. List experiments (including E2B runs): + mcp__genesis__list_experiments() + +2. Launch E2B experiment: + mcp__genesis__launch_experiment( + variant="squeeze_hnsw_e2b", # E2B variant + generations=50 + ) + +3. Get metrics from E2B run: + mcp__genesis__get_experiment_metrics( + run_path="genesis_squeeze_hnsw_e2b/2025..." + ) + +4. Read best code discovered: + mcp__genesis__read_best_code( + run_path="genesis_squeeze_hnsw_e2b/2025..." + ) + +The MCP server handles E2B configuration automatically +based on the variant chosen. +""") + + +def main(): + """Run all demos.""" + print("\n" + "#" * 60) + print("# Genesis E2B Backend Demonstration") + print("#" * 60) + + # Check for E2B API key + api_key = os.environ.get("E2B_API_KEY") + if not api_key: + print("\n" + "!" * 60) + print("WARNING: E2B_API_KEY not set!") + print("Set it with: export E2B_API_KEY=your_api_key") + print("Get your key from: https://e2b.dev") + print("!" * 60) + run_live = False + else: + print(f"\nE2B API key detected: {api_key[:8]}...") + run_live = True + + # Run demos + if run_live: + try: + demo_1_direct_e2b_usage() + except Exception as e: + print(f"\nDemo 1 failed (E2B may not be available): {e}") + else: + print("\n[Skipping Demo 1 - requires E2B_API_KEY]") + + demo_2_job_scheduler() + demo_3_hydra_config() + demo_4_full_evolution() + demo_5_e2b_with_mcp() + + print("\n" + "=" * 60) + print("Demonstration complete!") + print("=" * 60) + print(""" +Summary: +- E2B provides isolated cloud sandboxes for code evaluation +- Genesis supports E2B through multiple interfaces: + 1. Direct e2b module (genesis.launch.e2b) + 2. JobScheduler abstraction (genesis.launch.JobScheduler) + 3. Hydra config (genesis_launch +cluster=e2b) + 4. MCP server (genesis_launch_experiment) + +Key benefits of E2B backend: +- Parallel execution (10+ sandboxes simultaneously) +- Isolated environments (no interference between jobs) +- Automatic cleanup (ephemeral sandboxes) +- No local resource constraints +- Safe code execution (untrusted code runs in sandbox) +""") + + +if __name__ == "__main__": + main() diff --git a/examples/e2b_demo/run_circle_packing_e2b.py b/examples/e2b_demo/run_circle_packing_e2b.py new file mode 100644 index 0000000..20011f7 --- /dev/null +++ b/examples/e2b_demo/run_circle_packing_e2b.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +""" +Run Circle Packing Optimization with E2B Backend + +This script demonstrates running the circle packing example +using E2B cloud sandboxes for evaluation. + +Usage: + # Set your E2B API key first + export E2B_API_KEY=your_api_key + + # Run the demo + python run_circle_packing_e2b.py +""" + +import os +import sys +from pathlib import Path + +# Add Genesis to path +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + + +def create_e2b_config(): + """Create configuration for E2B-backed evolution.""" + from genesis.launch import E2BJobConfig + + return E2BJobConfig( + eval_program_path="evaluate.py", + template="base", + timeout=120, # 2 minutes per evaluation + dependencies=["numpy"], + additional_files={}, + env_vars={"PYTHONUNBUFFERED": "1"}, + ) + + +def main(): + """Run circle packing with E2B backend.""" + print("=" * 60) + print("Circle Packing Optimization with E2B Backend") + print("=" * 60) + + # Check for API key + if not os.environ.get("E2B_API_KEY"): + print("\nERROR: E2B_API_KEY not set!") + print("Please run: export E2B_API_KEY=your_api_key") + print("Get your key from: https://e2b.dev") + sys.exit(1) + + # Show configuration + config = create_e2b_config() + print(f"\nE2B Configuration:") + print(f" Template: {config.template}") + print(f" Timeout: {config.timeout}s") + print(f" Dependencies: {config.dependencies}") + + # Path to circle packing example + example_dir = Path(__file__).parent.parent / "circle_packing" + initial_py = example_dir / "initial.py" + evaluate_py = example_dir / "evaluate.py" + + if not initial_py.exists(): + print(f"\nERROR: {initial_py} not found!") + sys.exit(1) + + print(f"\nTask files:") + print(f" Initial code: {initial_py}") + print(f" Evaluator: {evaluate_py}") + + print("\n" + "-" * 60) + print("To run full evolution with E2B, use:") + print("-" * 60) + print(""" + # Option 1: CLI with Hydra + genesis_launch \\ + +task=circle_packing \\ + +cluster=e2b \\ + evo_config.num_generations=20 \\ + evo_config.max_parallel_jobs=5 + + # Option 2: Python API + from genesis.core import EvolutionRunner, EvolutionConfig + from genesis.launch import E2BJobConfig + from genesis.database import DatabaseConfig + + runner = EvolutionRunner( + evo_config=EvolutionConfig( + job_type="e2b", + num_generations=20, + max_parallel_jobs=5, + ), + job_config=E2BJobConfig( + eval_program_path="evaluate.py", + template="base", + timeout=120, + dependencies=["numpy"], + ), + db_config=DatabaseConfig(num_islands=2), + task_name="circle_packing", + ) + best = runner.run() +""") + + # Quick test: evaluate initial code in E2B + print("\n" + "=" * 60) + print("Testing: Evaluate initial.py in E2B sandbox") + print("=" * 60) + + from genesis.launch.e2b import submit_with_files, download_results, cleanup_sandbox + import tempfile + import json + + with tempfile.TemporaryDirectory() as tmpdir: + results_dir = Path(tmpdir) / "results" + results_dir.mkdir() + + print(f"\nSubmitting to E2B...") + job_id = submit_with_files( + log_dir=str(results_dir), + exec_fname=str(initial_py), + eval_program_path=str(evaluate_py), + timeout=120, + template="base", + verbose=True, + dependencies=["numpy"], + ) + print(f"Job ID: {job_id}") + + print(f"\nDownloading results...") + success = download_results(job_id, verbose=True) + + if success: + metrics_file = results_dir / "metrics.json" + correct_file = results_dir / "correct.json" + + if metrics_file.exists(): + with open(metrics_file) as f: + metrics = json.load(f) + print(f"\nMetrics:") + print(f" Combined score: {metrics.get('combined_score', 'N/A')}") + if "public" in metrics: + print(f" Num circles: {metrics['public'].get('num_circles', 'N/A')}") + + if correct_file.exists(): + with open(correct_file) as f: + correct = json.load(f) + print(f"\nValidation: {'PASSED' if correct.get('correct') else 'FAILED'}") + if correct.get("error"): + print(f" Error: {correct['error']}") + + # Show logs if available + log_file = results_dir / "job_log.out" + if log_file.exists(): + print(f"\nEvaluation log:") + print("-" * 40) + print(log_file.read_text()[:500]) + + cleanup_sandbox(job_id, verbose=True) + print("\nDone!") + + +if __name__ == "__main__": + main() diff --git a/genesis/launch/e2b.py b/genesis/launch/e2b.py index 68b51f3..eaccc07 100644 --- a/genesis/launch/e2b.py +++ b/genesis/launch/e2b.py @@ -107,8 +107,8 @@ def submit( logger.info(f"Creating E2B sandbox for job {job_id}...") try: - # Create sandbox with the specified template - sandbox = Sandbox( + # E2B v2.x uses Sandbox.create() instead of constructor + sandbox = Sandbox.create( template=template, timeout=timeout, api_key=get_e2b_api_key(), @@ -225,7 +225,8 @@ def submit_with_files( logger.info(f"Creating E2B sandbox for job {job_id}...") try: - sandbox = Sandbox( + # E2B v2.x uses Sandbox.create() instead of constructor + sandbox = Sandbox.create( template=template, timeout=timeout, api_key=get_e2b_api_key(), diff --git a/genesis/webui/frontend/package-lock.json b/genesis/webui/frontend/package-lock.json index c4ba8c0..49da1f6 100644 --- a/genesis/webui/frontend/package-lock.json +++ b/genesis/webui/frontend/package-lock.json @@ -8,13 +8,24 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@types/d3": "^7.4.3", + "@types/diff": "^7.0.2", + "chart.js": "^4.5.1", + "d3": "^7.9.0", + "diff": "^8.0.2", + "highlight.js": "^11.11.1", + "marked": "^17.0.1", + "plotly.js": "^3.3.0", "react": "^19.1.1", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", + "react-plotly.js": "^2.6.0", "react-router-dom": "^7.9.4" }, "devDependencies": { "@eslint/js": "^9.36.0", "@types/node": "^24.6.0", + "@types/plotly.js": "^3.0.8", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", @@ -25,6 +36,7 @@ "eslint-plugin-react-refresh": "^0.4.22", "eslint-plugin-react-x": "^2.1.0", "globals": "^16.4.0", + "oxlint": "^1.29.0", "prettier": "^3.6.2", "typescript": "~5.9.3", "typescript-eslint": "^8.45.0", @@ -62,6 +74,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -313,6 +326,24 @@ "node": ">=6.9.0" } }, + "node_modules/@choojs/findup": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", + "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", + "license": "MIT", + "dependencies": { + "commander": "^2.15.1" + }, + "bin": { + "findup": "bin/findup.js" + } + }, + "node_modules/@choojs/findup/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.10", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", @@ -1112,6 +1143,116 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "license": "ISC", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/geojson-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz", + "integrity": "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==", + "license": "ISC" + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", + "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==", + "license": "BSD-3-Clause", + "peerDependencies": { + "mapbox-gl": ">=0.32.1 <2.0.0" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", + "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", + "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.4.0.tgz", + "integrity": "sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@maplibre/maplibre-gl-style-spec/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1150,6 +1291,246 @@ "node": ">= 8" } }, + "node_modules/@oxlint/darwin-arm64": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.29.0.tgz", + "integrity": "sha512-XYsieDAI0kXJyvayHnmOW1qVydqklRRVT4O5eZmO/rdNCku5CoXsZvBvkPc3U8/9V1mRuen1sxbM9T5JsZqhdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint/darwin-x64": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.29.0.tgz", + "integrity": "sha512-s+Ch5/4zDJ6wsOk95xY3BS5mtE2JzHLz7gVZ9OWA9EvhVO84wz2YbDp2JaA314yyqhlX5SAkZ6fj3BRMIcQIqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint/linux-arm64-gnu": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.29.0.tgz", + "integrity": "sha512-qLCgdUkDBG8muK1o3mPgf31rvCPzj1Xff9DHlJjfv+B0ee/hJ2LAoK8EIsQedfQuuiAccOe9GG65BivGCTgKOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-arm64-musl": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.29.0.tgz", + "integrity": "sha512-qe62yb1fyW51wo1VBpx9AJJ1Ih1T8NYDeR9AmpNGkrmKN8u3pPbcGXM4mCrOwpwJUG9M/oFvCIlIz2RhawHlkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-x64-gnu": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.29.0.tgz", + "integrity": "sha512-4x7p2iVoSE2aT9qI1JOLxUAv3UuzMYGBYWBA4ZF8ln99AdUo1eo0snFacPNd6I/ZZNcv5TegXC+0EUhp5MfYBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-x64-musl": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.29.0.tgz", + "integrity": "sha512-BdH5gdRpaYpyZn2Zm+MCS4b1YmXNe7QyQhw0fawuou+N1LrdAyELgvqI5xXZ1MXCgWDOa6WJaoE6VOPaDc29GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/win32-arm64": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.29.0.tgz", + "integrity": "sha512-y+j9ZDrnMxvRTNIstZKFY7gJD07nT++c4cGmub1ENvhoHVToiQAAZQUOLDhXXRzCrFoG/cFJXJf72uowHZPbcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/win32-x64": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.29.0.tgz", + "integrity": "sha512-F1iRtq8VT96lT8hqOubLyV0GxgIK/XdXk2kFLXdCspiI2ngXeNmTTvmPxrj+WFL6fpJPgv7VKWRb/zEHJnNOrg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@plotly/d3": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.2.tgz", + "integrity": "sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA==", + "license": "BSD-3-Clause" + }, + "node_modules/@plotly/d3-sankey": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@plotly/d3-sankey/-/d3-sankey-0.7.2.tgz", + "integrity": "sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1", + "d3-collection": "1", + "d3-shape": "^1.2.0" + } + }, + "node_modules/@plotly/d3-sankey-circular": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@plotly/d3-sankey-circular/-/d3-sankey-circular-0.33.1.tgz", + "integrity": "sha512-FgBV1HEvCr3DV7RHhDsPXyryknucxtfnLwPtCKKxdolKyTFYoLX/ibEfX39iFYIL7DYbVeRtP43dbFcrHNE+KQ==", + "license": "MIT", + "dependencies": { + "d3-array": "^1.2.1", + "d3-collection": "^1.0.4", + "d3-shape": "^1.2.0", + "elementary-circuits-directed-graph": "^1.0.4" + } + }, + "node_modules/@plotly/d3-sankey-circular/node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/@plotly/d3-sankey-circular/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/@plotly/d3-sankey-circular/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/@plotly/d3-sankey/node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/@plotly/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/@plotly/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/@plotly/mapbox-gl": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@plotly/mapbox-gl/-/mapbox-gl-1.13.4.tgz", + "integrity": "sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ==", + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/@plotly/point-cluster": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@plotly/point-cluster/-/point-cluster-3.1.9.tgz", + "integrity": "sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw==", + "license": "MIT", + "dependencies": { + "array-bounds": "^1.0.1", + "binary-search-bounds": "^2.0.4", + "clamp": "^1.0.1", + "defined": "^1.0.0", + "dtype": "^2.0.0", + "flatten-vertex-data": "^1.0.2", + "is-obj": "^1.0.1", + "math-log2": "^1.0.1", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0" + } + }, + "node_modules/@plotly/regl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@plotly/regl/-/regl-2.1.2.tgz", + "integrity": "sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.38", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", @@ -1465,6 +1846,77 @@ "win32" ] }, + "node_modules/@turf/area": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-7.3.0.tgz", + "integrity": "sha512-N1feXkRzwiNakzloykPWwTBmZcmHFfL/raGMwJmZ9QoHQZqDNVFrI0bmuX8hALUJ8jzpt/c/91M1CIuRMF9vIw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.0", + "@turf/meta": "7.3.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.3.0.tgz", + "integrity": "sha512-EC5GSUJlhXSiCVCEmgCSheZYm0s1ouKzUNqeEOsEYlqTbMAZ19RWgsg/xH2tjnuUw2JP9eGAUzQnCFX6JEV53w==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.0", + "@turf/meta": "7.3.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-7.3.0.tgz", + "integrity": "sha512-6skXlwv6fCkdKsrkniizpEuC1IsAYQEyZG4XIzK5d3FGQTM/lVInLQP+wd9I6FLtW8b/6UbZcIjSU9wP/MMbTA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.0", + "@turf/meta": "7.3.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.3.0.tgz", + "integrity": "sha512-5kWdgwI6e2vGbkt2qOD+Z2BiKQ7dfKN/PtWRLCpvzyOO59rk19R53CHi8nUT/Y1vQLgWmT6eNpiKwsWwPZGIdg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.3.0.tgz", + "integrity": "sha512-fTLqdQqRm8qA2zHHUbBMY++YT9LDQejLG7OD70XF2dwg9nPiF9mUxO7nrsDp2IY8vNmH9OTAiMtlIjb0ssYccg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.0", + "@types/geojson": "^7946.0.10" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1510,81 +1962,396 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, - "node_modules/@types/node": { - "version": "24.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", - "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", - "dev": true, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "license": "MIT", "dependencies": { - "undici-types": "~7.14.0" + "@types/d3-selection": "*" } }, - "node_modules/@types/react": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", - "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", - "dev": true, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "@types/d3-selection": "*" } }, - "node_modules/@types/react-dom": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.1.tgz", - "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", - "dev": true, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", - "integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==", - "dev": true, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.0", - "@typescript-eslint/type-utils": "8.46.0", - "@typescript-eslint/utils": "8.46.0", - "@typescript-eslint/visitor-keys": "8.46.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.46.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@types/d3-selection": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/diff": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.2.tgz", + "integrity": "sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", + "license": "MIT" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, + "node_modules/@types/node": { + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "license": "MIT" + }, + "node_modules/@types/plotly.js": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/plotly.js/-/plotly.js-3.0.8.tgz", + "integrity": "sha512-FjmSFaLmHVgBIBL6H0yX5k/AB3a7FQzjKBlRUF8YT6HiXMArE+hbXYIZXZ/42SBrdL05LWEog0zPqEaIDNsAiw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz", + "integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.0", + "@typescript-eslint/type-utils": "8.46.0", + "@typescript-eslint/utils": "8.46.0", + "@typescript-eslint/visitor-keys": "8.46.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", @@ -1600,6 +2367,7 @@ "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -1846,12 +2614,19 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1909,6 +2684,42 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-bounds": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-bounds/-/array-bounds-1.0.1.tgz", + "integrity": "sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ==", + "license": "MIT" + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-normalize": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array-normalize/-/array-normalize-1.1.4.tgz", + "integrity": "sha512-fCp0wKFLjvSPmCn4F5Tiw4M3lpMZoHlCjfcs7nNzuj3vqQQ1/a8cgB9DXcpDSn18c+coLnaW7rqfcYCvKbyJXg==", + "license": "MIT", + "dependencies": { + "array-bounds": "^1.0.0" + } + }, + "node_modules/array-range": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-range/-/array-range-1.0.1.tgz", + "integrity": "sha512-shdaI1zT3CVNL2hnx9c0JMc0ZogGaxDs5e85akgHWKYa0yVbIyp06Ind3dVkTj/uuFrzaHBOyqFzo+VV6aXgtA==", + "license": "MIT" + }, + "node_modules/array-rearrange": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/array-rearrange/-/array-rearrange-2.2.2.tgz", + "integrity": "sha512-UfobP5N12Qm4Qu4fwLDIi2v6+wZsSf6snYSxAMeKhrh37YGnNWZPRmVEKc/2wfms53TLQnzfpG8wCx2Y/6NG1w==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1916,6 +2727,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.8.16", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", @@ -1926,6 +2746,12 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/binary-search-bounds": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz", + "integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==", + "license": "MIT" + }, "node_modules/birecord": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/birecord/-/birecord-0.1.1.tgz", @@ -1933,6 +2759,28 @@ "dev": true, "license": "(MIT OR Apache-2.0)" }, + "node_modules/bit-twiddle": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", + "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==", + "license": "MIT" + }, + "node_modules/bitmap-sdf": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bitmap-sdf/-/bitmap-sdf-1.0.4.tgz", + "integrity": "sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==", + "license": "MIT" + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1977,6 +2825,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -1991,6 +2840,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2022,6 +2877,15 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-fit": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/canvas-fit/-/canvas-fit-1.5.0.tgz", + "integrity": "sha512-onIcjRpz69/Hx5bB5HGbYKUF2uC6QT6Gp+pfpGm3A7mPfcluSLV5v4Zu+oflDUwLdUw0rLIBhUbi0v8hM4FJQQ==", + "license": "MIT", + "dependencies": { + "element-size": "^1.1.1" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2039,6 +2903,43 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/clamp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", + "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==", + "license": "MIT" + }, + "node_modules/color-alpha": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.0.4.tgz", + "integrity": "sha512-lr8/t5NPozTSqli+duAN+x+no/2WaKTeWvxhHGN+aXT6AJ8vPlzLa7UriyjWak0pSC2jHol9JgjBYnnHsGha9A==", + "license": "MIT", + "dependencies": { + "color-parse": "^1.3.8" + } + }, + "node_modules/color-alpha/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2052,33 +2953,120 @@ "node": ">=7.0.0" } }, + "node_modules/color-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/color-id/-/color-id-1.1.0.tgz", + "integrity": "sha512-2iRtAn6dC/6/G7bBIo0uupVrIne1NsQJvJxZOBCzQOfk7jRq97feaDZ3RdzuHakRXXnHGNwglto3pqtRx1sX0g==", + "license": "MIT", + "dependencies": { + "clamp": "^1.0.1" + } + }, "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "dev": true, - "license": "MIT" + "node_modules/color-normalize": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/color-normalize/-/color-normalize-1.5.0.tgz", + "integrity": "sha512-rUT/HDXMr6RFffrR53oX3HGWkDOP9goSAQGBkUaAYKjOE2JxozccdGyufageWDlInRAjm/jYPrf/Y38oa+7obw==", + "license": "MIT", + "dependencies": { + "clamp": "^1.0.1", + "color-rgba": "^2.1.1", + "dtype": "^2.0.0" + } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" + "node_modules/color-normalize/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0" + } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" + "node_modules/color-normalize/node_modules/color-rgba": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-2.4.0.tgz", + "integrity": "sha512-Nti4qbzr/z2LbUWySr7H9dk3Rl7gZt7ihHAxlgT4Ho90EXWkjtkL1avTleu9yeGuqrt/chxTB6GKK8nZZ6V0+Q==", + "license": "MIT", + "dependencies": { + "color-parse": "^1.4.2", + "color-space": "^2.0.0" + } + }, + "node_modules/color-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-2.0.0.tgz", + "integrity": "sha512-g2Z+QnWsdHLppAbrpcFWo629kLOnOPtpxYV69GCqm92gqSgyXbzlfyN3MXs0412fPBkFmiuS+rXposgBgBa6Kg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/color-rgba": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-3.0.0.tgz", + "integrity": "sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==", + "license": "MIT", + "dependencies": { + "color-parse": "^2.0.0", + "color-space": "^2.0.0" + } + }, + "node_modules/color-space": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/color-space/-/color-space-2.3.2.tgz", + "integrity": "sha512-BcKnbOEsOarCwyoLstcoEztwT0IJxqqQkNwDuA3a65sICvvHL2yoeV13psoDFh5IuiOMnIOKdQDwB4Mk3BypiA==", + "license": "Unlicense" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" }, "node_modules/cookie": { "version": "1.0.2", @@ -2089,6 +3077,18 @@ "node": ">=18" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/country-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/country-regex/-/country-regex-1.1.0.tgz", + "integrity": "sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2104,6 +3104,65 @@ "node": ">= 8" } }, + "node_modules/css-font": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-font/-/css-font-1.2.0.tgz", + "integrity": "sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA==", + "license": "MIT", + "dependencies": { + "css-font-size-keywords": "^1.0.0", + "css-font-stretch-keywords": "^1.0.1", + "css-font-style-keywords": "^1.0.1", + "css-font-weight-keywords": "^1.0.0", + "css-global-keywords": "^1.0.1", + "css-system-font-keywords": "^1.0.0", + "pick-by-alias": "^1.2.0", + "string-split-by": "^1.0.0", + "unquote": "^1.1.0" + } + }, + "node_modules/css-font-size-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz", + "integrity": "sha512-Q+svMDbMlelgCfH/RVDKtTDaf5021O486ZThQPIpahnIjUkMUslC+WuOQSWTgGSrNCH08Y7tYNEmmy0hkfMI8Q==", + "license": "MIT" + }, + "node_modules/css-font-stretch-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz", + "integrity": "sha512-KmugPO2BNqoyp9zmBIUGwt58UQSfyk1X5DbOlkb2pckDXFSAfjsD5wenb88fNrD6fvS+vu90a/tsPpb9vb0SLg==", + "license": "MIT" + }, + "node_modules/css-font-style-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz", + "integrity": "sha512-0Fn0aTpcDktnR1RzaBYorIxQily85M2KXRpzmxQPgh8pxUN9Fcn00I8u9I3grNr1QXVgCl9T5Imx0ZwKU973Vg==", + "license": "MIT" + }, + "node_modules/css-font-weight-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz", + "integrity": "sha512-5So8/NH+oDD+EzsnF4iaG4ZFHQ3vaViePkL1ZbZ5iC/KrsCY+WHq/lvOgrtmuOQ9pBBZ1ADGpaf+A4lj1Z9eYA==", + "license": "MIT" + }, + "node_modules/css-global-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-global-keywords/-/css-global-keywords-1.0.1.tgz", + "integrity": "sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ==", + "license": "MIT" + }, + "node_modules/css-system-font-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz", + "integrity": "sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==", + "license": "MIT" + }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2111,6 +3170,467 @@ "dev": true, "license": "MIT" }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-2.9.0.tgz", + "integrity": "sha512-ZULvK/zBn87of5rWAfFMc9mJOipeSo57O+BBitsKIXmU4rTVAnX1kSsJkE0R+TxY8pGNoM1nbyRRE7GYHhdOEQ==", + "license": "BSD-3-Clause", + "dependencies": { + "commander": "2", + "d3-array": "1", + "d3-geo": "^1.12.0", + "resolve": "^1.1.10" + }, + "bin": { + "geo2svg": "bin/geo2svg", + "geograticule": "bin/geograticule", + "geoproject": "bin/geoproject", + "geoquantize": "bin/geoquantize", + "geostitch": "bin/geostitch" + } + }, + "node_modules/d3-geo-projection/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/d3-geo-projection/node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-geo-projection/node_modules/d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2136,6 +3656,82 @@ "dev": true, "license": "MIT" }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/detect-kerning": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz", + "integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/draw-svg-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz", + "integrity": "sha512-P8j3IHxcgRMcY6sDzr0QvJDLzBnJJqpTG33UZ2Pvp8rw0apCHhJCWqYprqrXjrgHnJ6tuhP1iTJSAodPDHxwkg==", + "license": "MIT", + "dependencies": { + "abs-svg-path": "~0.1.1", + "normalize-svg-path": "~0.1.0" + } + }, + "node_modules/dtype": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dtype/-/dtype-2.0.0.tgz", + "integrity": "sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/dup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dup/-/dup-1.0.0.tgz", + "integrity": "sha512-Bz5jxMMC0wgp23Zm15ip1x8IhYRqJvF3nFC0UInJUDkN1z4uNPk9jTnfCUJXbOGiQ1JbXLQsiV41Fb+HXcj5BA==", + "license": "MIT" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, "node_modules/electron-to-chromium": { "version": "1.5.234", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz", @@ -2143,6 +3739,82 @@ "dev": true, "license": "ISC" }, + "node_modules/element-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/element-size/-/element-size-1.1.1.tgz", + "integrity": "sha512-eaN+GMOq/Q+BIWy0ybsgpcYImjGIdNLyjLFJU4XsLHXYQao5jCNb36GyN6C2qwmDDYSfIBmKpPpr4VnBdLCsPQ==", + "license": "MIT" + }, + "node_modules/elementary-circuits-directed-graph": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/elementary-circuits-directed-graph/-/elementary-circuits-directed-graph-1.3.1.tgz", + "integrity": "sha512-ZEiB5qkn2adYmpXGnJKkxT8uJHlW/mxmBpmeqawEHzPxh9HkLD4/1mFYX5l0On+f6rcPIt8/EWlRU2Vo3fX6dQ==", + "license": "MIT", + "dependencies": { + "strongly-connected-components": "^1.0.1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/esbuild": { "version": "0.25.10", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", @@ -2208,12 +3880,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "9.37.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2397,6 +4091,21 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -2415,6 +4124,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -2435,30 +4157,81 @@ "dev": true, "license": "BSD-2-Clause", "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/falafel": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.5.tgz", + "integrity": "sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==", + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "isarray": "^2.0.1" + }, "engines": { - "node": ">=4.0" + "node": ">=0.4.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/falafel/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=0.10.0" + "node": ">=0.4.0" } }, "node_modules/fast-deep-equal": { @@ -2498,6 +4271,15 @@ "node": ">= 6" } }, + "node_modules/fast-isnumeric": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-isnumeric/-/fast-isnumeric-1.1.4.tgz", + "integrity": "sha512-1mM8qOr2LYz8zGaUdmiqRDiuue00Dxjgcb1NQR7TnhLVh6sQyngP9xvLo7Sl7LZpP/sk5eb+bcyWXw530NTBZw==", + "license": "MIT", + "dependencies": { + "is-string-blank": "^1.0.1" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2586,6 +4368,43 @@ "dev": true, "license": "ISC" }, + "node_modules/flatten-vertex-data": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz", + "integrity": "sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw==", + "license": "MIT", + "dependencies": { + "dtype": "^2.0.0" + } + }, + "node_modules/font-atlas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/font-atlas/-/font-atlas-2.1.0.tgz", + "integrity": "sha512-kP3AmvX+HJpW4w3d+PiPR2X6E1yvsBXt2yhuCw+yReO9F1WYhvZwx3c95DGZGwg9xYzDGrgJYa885xmVA+28Cg==", + "license": "MIT", + "dependencies": { + "css-font": "^1.0.0" + } + }, + "node_modules/font-measure": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/font-measure/-/font-measure-1.2.2.tgz", + "integrity": "sha512-mRLEpdrWzKe9hbfaF3Qpr06TAjquuBVP5cHy4b3hyeNdjc9i0PO6HniGsX5vjL5OWv7+Bd++NiooNpT/s8BvIA==", + "license": "MIT", + "dependencies": { + "css-font": "^1.2.0" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2601,6 +4420,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2611,6 +4439,82 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==", + "license": "ISC" + }, + "node_modules/get-canvas-context": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-canvas-context/-/get-canvas-context-1.0.2.tgz", + "integrity": "sha512-LnpfLf/TNzr9zVOGiIY6aKCz8EKuXmlYNV7CM2pUjBa/B+c2I15tS7KLySep75+FuerJdmArvJLcsAXWEy2H0A==", + "license": "MIT" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gl-mat4": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz", + "integrity": "sha512-sT5C0pwB1/e9G9AvAoLsoaJtbMGjfd/jfxo8jMCKqYYEnjZuFvqV5rehqar0538EmssjdDeiEWnKyBSTw7quoA==", + "license": "Zlib" + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/gl-text": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gl-text/-/gl-text-1.4.0.tgz", + "integrity": "sha512-o47+XBqLCj1efmuNyCHt7/UEJmB9l66ql7pnobD6p+sgmBUdzfMZXIF0zD2+KRfpd99DJN+QXdvTFAGCKCVSmQ==", + "license": "MIT", + "dependencies": { + "bit-twiddle": "^1.0.2", + "color-normalize": "^1.5.0", + "css-font": "^1.2.0", + "detect-kerning": "^2.1.2", + "es6-weak-map": "^2.0.3", + "flatten-vertex-data": "^1.0.2", + "font-atlas": "^2.1.0", + "font-measure": "^1.2.2", + "gl-util": "^3.1.2", + "is-plain-obj": "^1.1.0", + "object-assign": "^4.1.1", + "parse-rect": "^1.2.0", + "parse-unit": "^1.0.1", + "pick-by-alias": "^1.2.0", + "regl": "^2.0.0", + "to-px": "^1.0.1", + "typedarray-pool": "^1.1.0" + } + }, + "node_modules/gl-util": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/gl-util/-/gl-util-3.1.3.tgz", + "integrity": "sha512-dvRTggw5MSkJnCbh74jZzSoTOGnVYK+Bt+Ckqm39CVcl6+zSsxqWk4lr5NKhkqXHL6qvZAU9h17ZF8mIskY9mA==", + "license": "MIT", + "dependencies": { + "is-browser": "^2.0.1", + "is-firefox": "^1.0.3", + "is-plain-obj": "^1.1.0", + "number-is-integer": "^1.0.1", + "object-assign": "^4.1.0", + "pick-by-alias": "^1.2.0", + "weak-map": "^1.0.5" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2624,6 +4528,44 @@ "node": ">=10.13.0" } }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "license": "MIT", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/globals": { "version": "16.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", @@ -2637,6 +4579,213 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glsl-inject-defines": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz", + "integrity": "sha512-W49jIhuDtF6w+7wCMcClk27a2hq8znvHtlGnrYkSWEr8tHe9eA2dcnohlcAmxLYBSpSSdzOkRdyPTrx9fw49+A==", + "license": "MIT", + "dependencies": { + "glsl-token-inject-block": "^1.0.0", + "glsl-token-string": "^1.0.1", + "glsl-tokenizer": "^2.0.2" + } + }, + "node_modules/glsl-resolve": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz", + "integrity": "sha512-xxFNsfnhZTK9NBhzJjSBGX6IOqYpvBHxxmo+4vapiljyGNCY0Bekzn0firQkQrazK59c1hYxMDxYS8MDlhw4gA==", + "license": "MIT", + "dependencies": { + "resolve": "^0.6.1", + "xtend": "^2.1.2" + } + }, + "node_modules/glsl-resolve/node_modules/resolve": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", + "integrity": "sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg==", + "license": "MIT" + }, + "node_modules/glsl-resolve/node_modules/xtend": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", + "integrity": "sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/glsl-token-assignments": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz", + "integrity": "sha512-OwXrxixCyHzzA0U2g4btSNAyB2Dx8XrztY5aVUCjRSh4/D0WoJn8Qdps7Xub3sz6zE73W3szLrmWtQ7QMpeHEQ==", + "license": "MIT" + }, + "node_modules/glsl-token-defines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz", + "integrity": "sha512-Vb5QMVeLjmOwvvOJuPNg3vnRlffscq2/qvIuTpMzuO/7s5kT+63iL6Dfo2FYLWbzuiycWpbC0/KV0biqFwHxaQ==", + "license": "MIT", + "dependencies": { + "glsl-tokenizer": "^2.0.0" + } + }, + "node_modules/glsl-token-depth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz", + "integrity": "sha512-eQnIBLc7vFf8axF9aoi/xW37LSWd2hCQr/3sZui8aBJnksq9C7zMeUYHVJWMhFzXrBU7fgIqni4EhXVW4/krpg==", + "license": "MIT" + }, + "node_modules/glsl-token-descope": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz", + "integrity": "sha512-kS2PTWkvi/YOeicVjXGgX5j7+8N7e56srNDEHDTVZ1dcESmbmpmgrnpjPcjxJjMxh56mSXYoFdZqb90gXkGjQw==", + "license": "MIT", + "dependencies": { + "glsl-token-assignments": "^2.0.0", + "glsl-token-depth": "^1.1.0", + "glsl-token-properties": "^1.0.0", + "glsl-token-scope": "^1.1.0" + } + }, + "node_modules/glsl-token-inject-block": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz", + "integrity": "sha512-q/m+ukdUBuHCOtLhSr0uFb/qYQr4/oKrPSdIK2C4TD+qLaJvqM9wfXIF/OOBjuSA3pUoYHurVRNao6LTVVUPWA==", + "license": "MIT" + }, + "node_modules/glsl-token-properties": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz", + "integrity": "sha512-dSeW1cOIzbuUoYH0y+nxzwK9S9O3wsjttkq5ij9ZGw0OS41BirKJzzH48VLm8qLg+au6b0sINxGC0IrGwtQUcA==", + "license": "MIT" + }, + "node_modules/glsl-token-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz", + "integrity": "sha512-YKyOMk1B/tz9BwYUdfDoHvMIYTGtVv2vbDSLh94PT4+f87z21FVdou1KNKgF+nECBTo0fJ20dpm0B1vZB1Q03A==", + "license": "MIT" + }, + "node_modules/glsl-token-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz", + "integrity": "sha512-1mtQ47Uxd47wrovl+T6RshKGkRRCYWhnELmkEcUAPALWGTFe2XZpH3r45XAwL2B6v+l0KNsCnoaZCSnhzKEksg==", + "license": "MIT" + }, + "node_modules/glsl-token-whitespace-trim": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz", + "integrity": "sha512-ZJtsPut/aDaUdLUNtmBYhaCmhIjpKNg7IgZSfX5wFReMc2vnj8zok+gB/3Quqs0TsBSX/fGnqUUYZDqyuc2xLQ==", + "license": "MIT" + }, + "node_modules/glsl-tokenizer": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz", + "integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==", + "license": "MIT", + "dependencies": { + "through2": "^0.6.3" + } + }, + "node_modules/glsl-tokenizer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/glsl-tokenizer/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/glsl-tokenizer/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/glsl-tokenizer/node_modules/through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==", + "license": "MIT", + "dependencies": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/glslify": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glslify/-/glslify-7.1.1.tgz", + "integrity": "sha512-bud98CJ6kGZcP9Yxcsi7Iz647wuDz3oN+IZsjCRi5X1PI7t/xPKeL0mOwXJjo+CRZMqvq0CkSJiywCcY7kVYog==", + "license": "MIT", + "dependencies": { + "bl": "^2.2.1", + "concat-stream": "^1.5.2", + "duplexify": "^3.4.5", + "falafel": "^2.1.0", + "from2": "^2.3.0", + "glsl-resolve": "0.0.1", + "glsl-token-whitespace-trim": "^1.0.0", + "glslify-bundle": "^5.0.0", + "glslify-deps": "^1.2.5", + "minimist": "^1.2.5", + "resolve": "^1.1.5", + "stack-trace": "0.0.9", + "static-eval": "^2.0.5", + "through2": "^2.0.1", + "xtend": "^4.0.0" + }, + "bin": { + "glslify": "bin.js" + } + }, + "node_modules/glslify-bundle": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.1.1.tgz", + "integrity": "sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A==", + "license": "MIT", + "dependencies": { + "glsl-inject-defines": "^1.0.1", + "glsl-token-defines": "^1.0.0", + "glsl-token-depth": "^1.1.1", + "glsl-token-descope": "^1.0.2", + "glsl-token-scope": "^1.1.1", + "glsl-token-string": "^1.0.1", + "glsl-token-whitespace-trim": "^1.0.0", + "glsl-tokenizer": "^2.0.2", + "murmurhash-js": "^1.0.0", + "shallow-copy": "0.0.1" + } + }, + "node_modules/glslify-deps": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.2.tgz", + "integrity": "sha512-7S7IkHWygJRjcawveXQjRXLO2FTjijPDYC7QfZyAQanY+yGLCFHYnPtsGT9bdyHiwPTw/5a1m1M9hamT2aBpag==", + "license": "ISC", + "dependencies": { + "@choojs/findup": "^0.2.0", + "events": "^3.2.0", + "glsl-resolve": "0.0.1", + "glsl-tokenizer": "^2.0.0", + "graceful-fs": "^4.1.2", + "inherits": "^2.0.1", + "map-limit": "0.0.1", + "resolve": "^1.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2644,6 +4793,12 @@ "dev": true, "license": "MIT" }, + "node_modules/grid-index": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", + "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==", + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2654,6 +4809,77 @@ "node": ">=8" } }, + "node_modules/has-hover": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-hover/-/has-hover-1.0.1.tgz", + "integrity": "sha512-0G6w7LnlcpyDzpeGUTuT0CEw05+QlMuGVk1IHNAlHrGJITGodjZu3x8BNDUMfKJSZXNB2ZAclqc1bvrd+uUpfg==", + "license": "MIT", + "dependencies": { + "is-browser": "^2.0.1" + } + }, + "node_modules/has-passive-events": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-passive-events/-/has-passive-events-1.0.0.tgz", + "integrity": "sha512-2vSj6IeIsgvsRMyeQ0JaCX5Q3lX4zMn5HpoVc7MEhQ6pv8Iq9rsXjsp+E5ZwaT7T0xhMT0KmU8gtt1EFVdbJiw==", + "license": "MIT", + "dependencies": { + "is-browser": "^2.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2675,20 +4901,65 @@ "resolve-from": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz", + "integrity": "sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-extglob": { @@ -2701,6 +4972,27 @@ "node": ">=0.10.0" } }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-firefox": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-firefox/-/is-firefox-1.0.3.tgz", + "integrity": "sha512-6Q9ITjvWIm0Xdqv+5U12wgOKEM2KoBw4Y926m0OFkvlCxnbG94HKAsVz8w3fWcfAS5YA2fJORXX1dLrkprCCxA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2714,6 +5006,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-iexplorer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-iexplorer/-/is-iexplorer-1.0.0.tgz", + "integrity": "sha512-YeLzceuwg3K6O0MLM3UyUUjKAlyULetwryFp1mHy1I5PfArK0AEqlfa+MR4gkJjcbuJXoDJCvXbyqZVf5CR2Sg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-immutable-type": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/is-immutable-type/-/is-immutable-type-5.0.1.tgz", @@ -2730,6 +5031,12 @@ "typescript": ">=4.7.4" } }, + "node_modules/is-mobile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-4.0.0.tgz", + "integrity": "sha512-mlcHZA84t1qLSuWkt2v0I2l61PYdyQDt4aG1mLIXF5FDMm4+haBCxCPYSr/uwqQNRk1MiTizn0ypEuRAOLRAew==", + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2740,6 +5047,42 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string-blank": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", + "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==", + "license": "MIT" + }, + "node_modules/is-svg-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-svg-path/-/is-svg-path-1.0.2.tgz", + "integrity": "sha512-Lj4vePmqpPR1ZnRctHv8ltSh1OrSxHkhUkd7wi+VQdcdP15/KvQFyk7LhNuM7ZW0EVbJz8kZLVmL9quLrfq4Kg==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2751,7 +5094,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -2801,6 +5143,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2814,6 +5162,12 @@ "node": ">=6" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2824,6 +5178,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2858,9 +5221,20 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2871,6 +5245,171 @@ "yallist": "^3.0.2" } }, + "node_modules/map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg==", + "license": "MIT", + "dependencies": { + "once": "~1.3.0" + } + }, + "node_modules/map-limit/node_modules/once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/mapbox-gl": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.3.tgz", + "integrity": "sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==", + "license": "SEE LICENSE IN LICENSE.txt", + "peer": true, + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/geojson-types": "^1.0.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^1.5.0", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^1.1.1", + "@mapbox/unitbezier": "^0.0.0", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.2", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.2.1", + "grid-index": "^1.1.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.1", + "quickselect": "^2.0.0", + "rw": "^1.3.3", + "supercluster": "^7.1.0", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/maplibre-gl": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", + "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@types/geojson": "^7946.0.14", + "@types/geojson-vt": "3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.0", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "global-prefix": "^4.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.3.0", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/@mapbox/tiny-sdf": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz", + "integrity": "sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==", + "license": "BSD-2-Clause" + }, + "node_modules/maplibre-gl/node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/maplibre-gl/node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, + "node_modules/maplibre-gl/node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, + "node_modules/maplibre-gl/node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-log2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz", + "integrity": "sha512-9W0yGtkaMAkf74XGYVy4Dqw3YUMnTNB2eeiw9aQbUl4A3KmuCEHTt2DgAB07ENzOYAjsYSAYufkAq0Zd+jU7zA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2908,11 +5447,57 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mouse-change": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", + "integrity": "sha512-vpN0s+zLL2ykyyUDh+fayu9Xkor5v/zRD9jhSqjRS1cJTGS0+oakVZzNm5n19JvvEj0you+MXlYTpNxUDQUjkQ==", + "license": "MIT", + "dependencies": { + "mouse-event": "^1.0.0" + } + }, + "node_modules/mouse-event": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/mouse-event/-/mouse-event-1.0.5.tgz", + "integrity": "sha512-ItUxtL2IkeSKSp9cyaX2JLUuKk2uMoxBg4bbOWVd29+CskYJR9BGsUqtXenNzKbnDshvupjUewDIYVrOB6NmGw==", + "license": "MIT" + }, + "node_modules/mouse-event-offset": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz", + "integrity": "sha512-s9sqOs5B1Ykox3Xo8b3Ss2IQju4UwlW6LSR+Q5FXWpprJ5fzMLefIIItr3PH8RwzfGy6gxs/4GAmiNuZScE25w==", + "license": "MIT" + }, + "node_modules/mouse-wheel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mouse-wheel/-/mouse-wheel-1.2.0.tgz", + "integrity": "sha512-+OfYBiUOCTWcTECES49neZwL5AoGkXE+lFjIvzwNCnYRlso+EnfvovcBxGoyQ0yQt806eSPjS675K0EwWknXmw==", + "license": "MIT", + "dependencies": { + "right-now": "^1.0.0", + "signum": "^1.0.0", + "to-px": "^1.0.1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "license": "MIT" + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", "license": "MIT" }, "node_modules/nanoid": { @@ -2934,6 +5519,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2941,6 +5532,50 @@ "dev": true, "license": "MIT" }, + "node_modules/needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/node-releases": { "version": "2.0.23", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", @@ -2948,6 +5583,42 @@ "dev": true, "license": "MIT" }, + "node_modules/normalize-svg-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz", + "integrity": "sha512-1/kmYej2iedi5+ROxkRESL/pI02pkg0OBnaR4hJkSIX6+ORzepwbuUXfrdZaPjysTsJInj0Rj5NuX027+dMBvA==", + "license": "MIT" + }, + "node_modules/number-is-integer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-integer/-/number-is-integer-1.0.1.tgz", + "integrity": "sha512-Dq3iuiFBkrbmuQjGFFF3zckXNCQoSD37/SdSbgcBailUx6knDvDwb5CympBgcoWHy36sfS12u74MHYkXyHq6bg==", + "license": "MIT", + "dependencies": { + "is-finite": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2966,6 +5637,41 @@ "node": ">= 0.8.0" } }, + "node_modules/oxlint": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.29.0.tgz", + "integrity": "sha512-YqUVUhTYDqazV2qu3QSQn/H4Z1OP+fTnedgZWDk1/lDZxGfR0b1MqRVaEm3rRjBMLHP0zXlriIWUx+DD6UMaPA==", + "dev": true, + "license": "MIT", + "bin": { + "oxc_language_server": "bin/oxc_language_server", + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/darwin-arm64": "1.29.0", + "@oxlint/darwin-x64": "1.29.0", + "@oxlint/linux-arm64-gnu": "1.29.0", + "@oxlint/linux-arm64-musl": "1.29.0", + "@oxlint/linux-x64-gnu": "1.29.0", + "@oxlint/linux-x64-musl": "1.29.0", + "@oxlint/win32-arm64": "1.29.0", + "@oxlint/win32-x64": "1.29.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.7.1" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + } + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3011,6 +5717,33 @@ "node": ">=6" } }, + "node_modules/parenthesis": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz", + "integrity": "sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==", + "license": "MIT" + }, + "node_modules/parse-rect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parse-rect/-/parse-rect-1.2.0.tgz", + "integrity": "sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA==", + "license": "MIT", + "dependencies": { + "pick-by-alias": "^1.2.0" + } + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, + "node_modules/parse-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz", + "integrity": "sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3031,6 +5764,37 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/pick-by-alias": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", + "integrity": "sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3051,6 +5815,151 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/plotly.js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.3.0.tgz", + "integrity": "sha512-3PT9dW7IbIfN7JWGr4YxxFQnbN5MRaB36qIKF/eF0iC9l0/MuGSlMlgRgI7Uu8vYuGxX6AjLwsBBRYTPG7NFSA==", + "license": "MIT", + "dependencies": { + "@plotly/d3": "3.8.2", + "@plotly/d3-sankey": "0.7.2", + "@plotly/d3-sankey-circular": "0.33.1", + "@plotly/mapbox-gl": "1.13.4", + "@plotly/regl": "^2.1.2", + "@turf/area": "^7.1.0", + "@turf/bbox": "^7.1.0", + "@turf/centroid": "^7.1.0", + "base64-arraybuffer": "^1.0.2", + "canvas-fit": "^1.5.0", + "color-alpha": "1.0.4", + "color-normalize": "1.5.0", + "color-parse": "2.0.0", + "color-rgba": "3.0.0", + "country-regex": "^1.1.0", + "d3-force": "^1.2.1", + "d3-format": "^1.4.5", + "d3-geo": "^1.12.1", + "d3-geo-projection": "^2.9.0", + "d3-hierarchy": "^1.1.9", + "d3-interpolate": "^3.0.1", + "d3-time": "^1.1.0", + "d3-time-format": "^2.2.3", + "fast-isnumeric": "^1.1.4", + "gl-mat4": "^1.2.0", + "gl-text": "^1.4.0", + "has-hover": "^1.0.1", + "has-passive-events": "^1.0.0", + "is-mobile": "^4.0.0", + "maplibre-gl": "^4.7.1", + "mouse-change": "^1.4.0", + "mouse-event-offset": "^3.0.2", + "mouse-wheel": "^1.2.0", + "native-promise-only": "^0.8.1", + "parse-svg-path": "^0.1.2", + "point-in-polygon": "^1.1.0", + "polybooljs": "^1.2.2", + "probe-image-size": "^7.2.3", + "regl-error2d": "^2.0.12", + "regl-line2d": "^3.1.3", + "regl-scatter2d": "^3.3.1", + "regl-splom": "^1.0.14", + "strongly-connected-components": "^1.0.1", + "superscript-text": "^1.0.0", + "svg-path-sdf": "^1.1.3", + "tinycolor2": "^1.4.2", + "to-px": "1.0.1", + "topojson-client": "^3.1.0", + "webgl-context": "^2.2.0", + "world-calendars": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/plotly.js/node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/plotly.js/node_modules/d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", + "license": "BSD-3-Clause" + }, + "node_modules/plotly.js/node_modules/d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "node_modules/plotly.js/node_modules/d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==", + "license": "BSD-3-Clause" + }, + "node_modules/plotly.js/node_modules/d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/plotly.js/node_modules/d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==", + "license": "BSD-3-Clause" + }, + "node_modules/plotly.js/node_modules/d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==", + "license": "BSD-3-Clause" + }, + "node_modules/plotly.js/node_modules/d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==", + "license": "BSD-3-Clause" + }, + "node_modules/plotly.js/node_modules/d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-time": "1" + } + }, + "node_modules/plotly.js/node_modules/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==", + "license": "BSD-3-Clause" + }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==", + "license": "MIT" + }, + "node_modules/polybooljs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/polybooljs/-/polybooljs-1.2.2.tgz", + "integrity": "sha512-ziHW/02J0XuNuUtmidBc6GXE8YohYydp3DWPWXYsd7O721TjcmN+k6ezjdwkDqep+gnWnFY+yqZHvzElra2oCg==", + "license": "MIT" + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3080,6 +5989,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3106,6 +6021,40 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/probe-image-size": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", + "integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==", + "license": "MIT", + "dependencies": { + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3137,20 +6086,47 @@ ], "license": "MIT" }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3158,6 +6134,25 @@ "react": "^19.2.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-plotly.js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-plotly.js/-/react-plotly.js-2.6.0.tgz", + "integrity": "sha512-g93xcyhAVCSt9kV1svqG1clAEdL6k3U+jjuSzfTV7owaSU9Go6Ph8bl25J+jKfKvIGAEYpe4qj++WHJuc9IaeA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "plotly.js": ">1.34.0", + "react": ">0.13.0" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -3206,6 +6201,151 @@ "react-dom": ">=18" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/regl": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/regl/-/regl-2.1.1.tgz", + "integrity": "sha512-+IOGrxl3FZ8ZM9ixCWQZzFRiRn7Rzn9bu3iFHwg/yz4tlOUQgbO4PHLgG+1ZT60zcIV8tief6Qrmyl8qcoJP0g==", + "license": "MIT" + }, + "node_modules/regl-error2d": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/regl-error2d/-/regl-error2d-2.0.12.tgz", + "integrity": "sha512-r7BUprZoPO9AbyqM5qlJesrSRkl+hZnVKWKsVp7YhOl/3RIpi4UDGASGJY0puQ96u5fBYw/OlqV24IGcgJ0McA==", + "license": "MIT", + "dependencies": { + "array-bounds": "^1.0.1", + "color-normalize": "^1.5.0", + "flatten-vertex-data": "^1.0.2", + "object-assign": "^4.1.1", + "pick-by-alias": "^1.2.0", + "to-float32": "^1.1.0", + "update-diff": "^1.1.0" + } + }, + "node_modules/regl-line2d": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/regl-line2d/-/regl-line2d-3.1.3.tgz", + "integrity": "sha512-fkgzW+tTn4QUQLpFKsUIE0sgWdCmXAM3ctXcCgoGBZTSX5FE2A0M7aynz7nrZT5baaftLrk9te54B+MEq4QcSA==", + "license": "MIT", + "dependencies": { + "array-bounds": "^1.0.1", + "array-find-index": "^1.0.2", + "array-normalize": "^1.1.4", + "color-normalize": "^1.5.0", + "earcut": "^2.1.5", + "es6-weak-map": "^2.0.3", + "flatten-vertex-data": "^1.0.2", + "object-assign": "^4.1.1", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0", + "to-float32": "^1.1.0" + } + }, + "node_modules/regl-scatter2d": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/regl-scatter2d/-/regl-scatter2d-3.3.1.tgz", + "integrity": "sha512-seOmMIVwaCwemSYz/y4WE0dbSO9svNFSqtTh5RE57I7PjGo3tcUYKtH0MTSoshcAsreoqN8HoCtnn8wfHXXfKQ==", + "license": "MIT", + "dependencies": { + "@plotly/point-cluster": "^3.1.9", + "array-range": "^1.0.1", + "array-rearrange": "^2.2.2", + "clamp": "^1.0.1", + "color-id": "^1.1.0", + "color-normalize": "^1.5.0", + "color-rgba": "^2.1.1", + "flatten-vertex-data": "^1.0.2", + "glslify": "^7.0.0", + "is-iexplorer": "^1.0.0", + "object-assign": "^4.1.1", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0", + "to-float32": "^1.1.0", + "update-diff": "^1.1.0" + } + }, + "node_modules/regl-scatter2d/node_modules/color-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.3.tgz", + "integrity": "sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/regl-scatter2d/node_modules/color-rgba": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/color-rgba/-/color-rgba-2.4.0.tgz", + "integrity": "sha512-Nti4qbzr/z2LbUWySr7H9dk3Rl7gZt7ihHAxlgT4Ho90EXWkjtkL1avTleu9yeGuqrt/chxTB6GKK8nZZ6V0+Q==", + "license": "MIT", + "dependencies": { + "color-parse": "^1.4.2", + "color-space": "^2.0.0" + } + }, + "node_modules/regl-splom": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/regl-splom/-/regl-splom-1.0.14.tgz", + "integrity": "sha512-OiLqjmPRYbd7kDlHC6/zDf6L8lxgDC65BhC8JirhP4ykrK4x22ZyS+BnY8EUinXKDeMgmpRwCvUmk7BK4Nweuw==", + "license": "MIT", + "dependencies": { + "array-bounds": "^1.0.1", + "array-range": "^1.0.1", + "color-alpha": "^1.0.4", + "flatten-vertex-data": "^1.0.2", + "parse-rect": "^1.2.0", + "pick-by-alias": "^1.2.0", + "raf": "^3.4.1", + "regl-scatter2d": "^3.2.3" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3216,6 +6356,15 @@ "node": ">=4" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3227,6 +6376,18 @@ "node": ">=0.10.0" } }, + "node_modules/right-now": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/right-now/-/right-now-1.0.0.tgz", + "integrity": "sha512-DA8+YS+sMIVpbsuKgy+Z67L9Lxb1p05mNxRpDPNksPDEFir4vmBlUtuN9jkTGn9YMMdlBuK7XQgFiz6ws+yhSg==", + "license": "MIT" + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.52.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", @@ -3293,6 +6454,44 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -3315,6 +6514,12 @@ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, + "node_modules/shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3338,6 +6543,22 @@ "node": ">=8" } }, + "node_modules/signum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/signum/-/signum-1.0.0.tgz", + "integrity": "sha512-yodFGwcyt59XRh7w5W3jPcIQb3Bwi21suEfT7MAWnBX3iCdklJpgDgvGT9o04UonglZN5SNMfJFkHIR/jO8GHw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3348,6 +6569,77 @@ "node": ">=0.10.0" } }, + "node_modules/stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", + "engines": { + "node": "*" + } + }, + "node_modules/static-eval": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", + "license": "MIT", + "dependencies": { + "escodegen": "^2.1.0" + } + }, + "node_modules/stream-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", + "license": "MIT", + "dependencies": { + "debug": "2" + } + }, + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-split-by": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string-split-by/-/string-split-by-1.0.0.tgz", + "integrity": "sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A==", + "license": "MIT", + "dependencies": { + "parenthesis": "^3.1.5" + } + }, "node_modules/string-ts": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.2.1.tgz", @@ -3368,6 +6660,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strongly-connected-components": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz", + "integrity": "sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==", + "license": "MIT" + }, + "node_modules/supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "license": "ISC", + "dependencies": { + "kdbush": "^3.0.0" + } + }, + "node_modules/supercluster/node_modules/kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==", + "license": "ISC" + }, + "node_modules/superscript-text": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/superscript-text/-/superscript-text-1.0.0.tgz", + "integrity": "sha512-gwu8l5MtRZ6koO0icVTlmN5pm7Dhh1+Xpe9O4x6ObMAsW+3jPbW14d1DsBq1F4wiI+WOFjXF35pslgec/G8yCQ==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3381,6 +6700,74 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", + "license": "ISC" + }, + "node_modules/svg-path-bounds": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz", + "integrity": "sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ==", + "license": "MIT", + "dependencies": { + "abs-svg-path": "^0.1.1", + "is-svg-path": "^1.0.1", + "normalize-svg-path": "^1.0.0", + "parse-svg-path": "^0.1.2" + } + }, + "node_modules/svg-path-bounds/node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "license": "MIT", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, + "node_modules/svg-path-sdf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/svg-path-sdf/-/svg-path-sdf-1.1.3.tgz", + "integrity": "sha512-vJJjVq/R5lSr2KLfVXVAStktfcfa1pNFjFOgyJnzZFXlO/fDZ5DmM8FpnSKKzLPfEYTVeXuVBTHF296TpxuJVg==", + "license": "MIT", + "dependencies": { + "bitmap-sdf": "^1.0.0", + "draw-svg-path": "^1.0.0", + "is-svg-path": "^1.0.1", + "parse-svg-path": "^0.1.2", + "svg-path-bounds": "^1.0.1" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -3422,6 +6809,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3429,6 +6817,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", + "license": "ISC" + }, + "node_modules/to-float32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/to-float32/-/to-float32-1.1.0.tgz", + "integrity": "sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg==", + "license": "MIT" + }, + "node_modules/to-px": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-px/-/to-px-1.0.1.tgz", + "integrity": "sha512-2y3LjBeIZYL19e5gczp14/uRWFDtDUErJPVN3VU9a7SJO+RjGRtYR47aMN2bZgGlxvW4ZcEz2ddUPVHXcMfuXw==", + "license": "MIT", + "dependencies": { + "parse-unit": "^1.0.1" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3442,6 +6851,26 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -3498,6 +6927,18 @@ "dev": true, "license": "MIT" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3511,12 +6952,29 @@ "node": ">= 0.8.0" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typedarray-pool": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typedarray-pool/-/typedarray-pool-1.2.0.tgz", + "integrity": "sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ==", + "license": "MIT", + "dependencies": { + "bit-twiddle": "^1.0.0", + "dup": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3556,6 +7014,12 @@ "dev": true, "license": "MIT" }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -3587,6 +7051,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-diff/-/update-diff-1.1.0.tgz", + "integrity": "sha512-rCiBPiHxZwT4+sBhEbChzpO5hYHjm91kScWgdHf4Qeafs6Ba7MBl+d9GlGv72bcTZQO0sLmtQS1pHSWoCLtN/A==", + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3597,12 +7067,19 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vite": { "version": "7.1.9", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3696,6 +7173,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3703,6 +7181,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, + "node_modules/weak-map": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", + "integrity": "sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw==", + "license": "Apache-2.0" + }, + "node_modules/webgl-context": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webgl-context/-/webgl-context-2.2.0.tgz", + "integrity": "sha512-q/fGIivtqTT7PEoF07axFIlHNk/XCPaYpq64btnepopSWvKNFkoORlQYgqDigBIuGA1ExnFd/GnSUnBNEPQY7Q==", + "license": "MIT", + "dependencies": { + "get-canvas-context": "^1.0.1" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3729,6 +7233,30 @@ "node": ">=0.10.0" } }, + "node_modules/world-calendars": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.4.tgz", + "integrity": "sha512-VGRnLJS+xJmGDPodgJRnGIDwGu0s+Cr9V2HB3EzlDZ5n0qb8h5SJtGUEkjrphZYAglEiXZ6kiXdmk0H/h/uu/w==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/genesis/webui/frontend/package.json b/genesis/webui/frontend/package.json index 2dbbddf..0322ec1 100644 --- a/genesis/webui/frontend/package.json +++ b/genesis/webui/frontend/package.json @@ -5,21 +5,33 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", + "build": "tsc -b && oxlint . && vite build", "lint": "eslint .", + "lint:oxc": "oxlint .", "lint:fix": "eslint . --fix", "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"", "preview": "vite preview" }, "dependencies": { + "@types/d3": "^7.4.3", + "@types/diff": "^7.0.2", + "chart.js": "^4.5.1", + "d3": "^7.9.0", + "diff": "^8.0.2", + "highlight.js": "^11.11.1", + "marked": "^17.0.1", + "plotly.js": "^3.3.0", "react": "^19.1.1", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", + "react-plotly.js": "^2.6.0", "react-router-dom": "^7.9.4" }, "devDependencies": { "@eslint/js": "^9.36.0", "@types/node": "^24.6.0", + "@types/plotly.js": "^3.0.8", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", @@ -30,6 +42,7 @@ "eslint-plugin-react-refresh": "^0.4.22", "eslint-plugin-react-x": "^2.1.0", "globals": "^16.4.0", + "oxlint": "^1.29.0", "prettier": "^3.6.2", "typescript": "~5.9.3", "typescript-eslint": "^8.45.0", diff --git a/genesis/webui/frontend/src/App.css b/genesis/webui/frontend/src/App.css index 6a1a58b..9527d3d 100644 --- a/genesis/webui/frontend/src/App.css +++ b/genesis/webui/frontend/src/App.css @@ -1,3 +1,4 @@ #root { - min-height: 100vh; + height: 100vh; + overflow: hidden; } diff --git a/genesis/webui/frontend/src/App.tsx b/genesis/webui/frontend/src/App.tsx index 3b53ffa..97cb272 100644 --- a/genesis/webui/frontend/src/App.tsx +++ b/genesis/webui/frontend/src/App.tsx @@ -1,18 +1,16 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; -import Layout from './components/Layout'; -import Home from './pages/Home'; -import About from './pages/About'; +import { GenesisProvider } from './context/GenesisContext'; +import VisualizationLayout from './components/VisualizationLayout'; import './App.css'; export default function App() { return ( - - - }> - } /> - } /> - - - + + + + } /> + + + ); } diff --git a/genesis/webui/frontend/src/components/DatabaseSelector.css b/genesis/webui/frontend/src/components/DatabaseSelector.css new file mode 100644 index 0000000..100e6db --- /dev/null +++ b/genesis/webui/frontend/src/components/DatabaseSelector.css @@ -0,0 +1,87 @@ +.database-selector { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + padding: 10px 15px; + background-color: #fff; + border-bottom: 1px solid #ddd; +} + +.database-selector label { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 500; + color: #333; +} + +.database-selector select { + padding: 5px 10px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: white; + min-width: 150px; + max-width: 200px; + font-size: 14px; +} + +.database-selector select:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); +} + +.icon-button { + background: #f8f9fa; + border: 1px solid #ddd; + border-radius: 4px; + padding: 5px 8px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s; +} + +.icon-button:hover:not(:disabled) { + background: #e9ecef; +} + +.icon-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.reload-button { + background: #ebf5ff; + border-color: #b3d7ff; +} + +.reload-button:hover:not(:disabled) { + background: #d4eaff; +} + +.auto-refresh-button { + background: #fff3cd; + border-color: #ffeaa7; +} + +.auto-refresh-button:hover:not(:disabled) { + background: #fff1b8; +} + +.auto-refresh-button.active { + background: #d4edda; + border-color: #c3e6cb; + color: #155724; +} + +.auto-refresh-button.active:hover:not(:disabled) { + background: #c1e5c5; +} + +.loading-indicator { + font-size: 12px; + color: #666; + margin-left: 10px; +} diff --git a/genesis/webui/frontend/src/components/DatabaseSelector.tsx b/genesis/webui/frontend/src/components/DatabaseSelector.tsx new file mode 100644 index 0000000..b46aead --- /dev/null +++ b/genesis/webui/frontend/src/components/DatabaseSelector.tsx @@ -0,0 +1,133 @@ +import { useEffect, useMemo } from 'react'; +import { useGenesis } from '../context/GenesisContext'; +import './DatabaseSelector.css'; + +export default function DatabaseSelector() { + const { + state, + loadDatabases, + loadDatabase, + refreshData, + setAutoRefresh, + } = useGenesis(); + const { tasksAndResults, currentDbPath, isLoading, autoRefreshEnabled } = + state; + + // Extract current task and result from path + const { currentTask, currentResult } = useMemo(() => { + if (!currentDbPath) return { currentTask: '', currentResult: '' }; + const parts = currentDbPath.split('/'); + if (parts.length >= 3) { + return { + currentTask: parts[parts.length - 3], + currentResult: currentDbPath, + }; + } + return { currentTask: '', currentResult: '' }; + }, [currentDbPath]); + + // Load databases on mount + useEffect(() => { + loadDatabases(); + }, [loadDatabases]); + + // Get sorted task names + const tasks = useMemo( + () => Object.keys(tasksAndResults).sort(), + [tasksAndResults] + ); + + // Get results for current task + const results = useMemo( + () => (currentTask ? tasksAndResults[currentTask] || [] : []), + [currentTask, tasksAndResults] + ); + + const handleTaskChange = (e: React.ChangeEvent) => { + const task = e.target.value; + if (task && tasksAndResults[task]?.length > 0) { + // Auto-select first result + loadDatabase(tasksAndResults[task][0].path); + } + }; + + const handleResultChange = (e: React.ChangeEvent) => { + const path = e.target.value; + if (path) { + loadDatabase(path); + } + }; + + const handleRefreshFiles = () => { + loadDatabases(true); + }; + + const handleReloadData = () => { + refreshData(); + }; + + const handleAutoRefreshToggle = () => { + setAutoRefresh(!autoRefreshEnabled); + }; + + return ( +
+ + + + + + + + + + + {isLoading && Loading...} +
+ ); +} diff --git a/genesis/webui/frontend/src/components/VisualizationLayout.css b/genesis/webui/frontend/src/components/VisualizationLayout.css new file mode 100644 index 0000000..0abe6cc --- /dev/null +++ b/genesis/webui/frontend/src/components/VisualizationLayout.css @@ -0,0 +1,57 @@ +.visualization-layout { + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; + background-color: #f8f9fa; +} + +.panels-container { + display: flex; + flex: 1; + overflow: hidden; +} + +.left-panel { + height: 100%; + overflow: hidden; + min-width: 200px; + background-color: #fff; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; +} + +.divider { + width: 8px; + background-color: #ddd; + cursor: col-resize; + flex-shrink: 0; + transition: background-color 0.2s; + position: relative; +} + +.divider:hover { + background-color: #3498db; +} + +.divider::after { + content: ''; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 30px; + background-color: #888; + border-radius: 1px; +} + +.right-panel { + height: 100%; + overflow: hidden; + min-width: 200px; + display: flex; + flex-direction: column; + background-color: #fff; +} diff --git a/genesis/webui/frontend/src/components/VisualizationLayout.tsx b/genesis/webui/frontend/src/components/VisualizationLayout.tsx new file mode 100644 index 0000000..5b07509 --- /dev/null +++ b/genesis/webui/frontend/src/components/VisualizationLayout.tsx @@ -0,0 +1,67 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import DatabaseSelector from './DatabaseSelector'; +import LeftPanel from './left-panel/LeftPanel'; +import RightPanel from './right-panel/RightPanel'; +import './VisualizationLayout.css'; + +export default function VisualizationLayout() { + const [leftPanelWidth, setLeftPanelWidth] = useState(50); // percentage + const containerRef = useRef(null); + const isDragging = useRef(false); + + const handleMouseDown = useCallback(() => { + isDragging.current = true; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, []); + + const handleMouseMove = useCallback((e: MouseEvent) => { + if (!isDragging.current || !containerRef.current) return; + + const containerRect = containerRef.current.getBoundingClientRect(); + const newWidth = + ((e.clientX - containerRect.left) / containerRect.width) * 100; + + // Enforce min/max constraints (20% - 80%) + const clampedWidth = Math.max(20, Math.min(80, newWidth)); + setLeftPanelWidth(clampedWidth); + }, []); + + const handleMouseUp = useCallback(() => { + isDragging.current = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }, []); + + useEffect(() => { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [handleMouseMove, handleMouseUp]); + + return ( +
+ +
+
+ +
+
+
+ +
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/BestPathView.css b/genesis/webui/frontend/src/components/left-panel/BestPathView.css new file mode 100644 index 0000000..69c4242 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/BestPathView.css @@ -0,0 +1,101 @@ +.best-path-view { + height: 100%; + padding: 20px; + overflow-y: auto; +} + +.best-path-view.empty { + display: flex; + align-items: center; + justify-content: center; + color: #666; +} + +.best-path-view h4 { + margin: 0 0 20px 0; + text-align: center; +} + +.timeline { + position: relative; + margin: 0 auto; + padding: 20px 0; + width: 90%; +} + +.timeline::after { + content: ''; + position: absolute; + width: 4px; + background-color: #e0e0e0; + top: 0; + bottom: 0; + left: 20px; +} + +.timeline-item { + padding: 10px 40px; + position: relative; + cursor: pointer; +} + +.timeline-marker { + position: absolute; + width: 20px; + height: 20px; + left: 11px; + background-color: white; + border: 4px solid #ff8c00; + border-radius: 50%; + z-index: 1; +} + +.timeline-content { + padding: 15px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; + transition: + border-color 0.2s, + box-shadow 0.2s; +} + +.timeline-content:hover { + border-color: #ff8c00; +} + +.timeline-item.selected .timeline-content { + border-color: #e58e26; + box-shadow: 0 0 8px rgba(229, 130, 38, 0.4); +} + +.timeline-content h5 { + margin: 0 0 8px 0; + color: #343a40; + display: flex; + align-items: center; + gap: 10px; +} + +.best-badge { + font-size: 12px; + background-color: #ffd700; + padding: 2px 6px; + border-radius: 3px; +} + +.timeline-content p { + margin: 5px 0; + font-size: 14px; + line-height: 1.5; +} + +.timeline-content .score { + color: #2ecc71; + font-weight: 600; +} + +.timeline-content .meta { + color: #666; + font-size: 13px; +} diff --git a/genesis/webui/frontend/src/components/left-panel/BestPathView.tsx b/genesis/webui/frontend/src/components/left-panel/BestPathView.tsx new file mode 100644 index 0000000..9558c5b --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/BestPathView.tsx @@ -0,0 +1,78 @@ +import { useMemo } from 'react'; +import { useGenesis } from '../../context/GenesisContext'; +import type { Program } from '../../types'; +import './BestPathView.css'; + +export default function BestPathView() { + const { state, stats, selectProgram, setRightTab } = useGenesis(); + const { programs, selectedProgram } = state; + const { bestProgram } = stats; + + // Build path to best program + const pathToBest = useMemo(() => { + if (!bestProgram) return []; + + const path: Program[] = []; + const programMap = new Map(programs.map((p) => [p.id, p])); + + let current: Program | undefined = bestProgram; + while (current) { + path.unshift(current); + if (current.parent_id) { + current = programMap.get(current.parent_id); + } else { + break; + } + } + + return path; + }, [bestProgram, programs]); + + const handleNodeClick = (program: Program) => { + selectProgram(program); + setRightTab('code-viewer'); + }; + + if (pathToBest.length === 0) { + return ( +
+

No best path available.

+
+ ); + } + + return ( +
+

Path to Best Solution

+
+ {pathToBest.map((program, index) => ( +
handleNodeClick(program)} + > +
+
+
+ Generation {program.generation} + {index === pathToBest.length - 1 && ( + 🏆 Best + )} +
+

+ {program.metadata.patch_name} +

+

+ Score: {program.combined_score?.toFixed(4) || 'N/A'} +

+

+ Type: {program.metadata.patch_type} | Island:{' '} + {program.island_idx ?? 'N/A'} +

+
+
+ ))} +
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/ClustersView.css b/genesis/webui/frontend/src/components/left-panel/ClustersView.css new file mode 100644 index 0000000..1f17427 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/ClustersView.css @@ -0,0 +1,22 @@ +.clusters-view { + height: 100%; + display: flex; + flex-direction: column; + padding: 20px; +} + +.clusters-view.empty { + align-items: center; + justify-content: center; + color: #666; +} + +.clusters-view h4 { + margin: 0 0 20px 0; + text-align: center; +} + +.clusters-content { + flex: 1; + overflow: auto; +} diff --git a/genesis/webui/frontend/src/components/left-panel/ClustersView.tsx b/genesis/webui/frontend/src/components/left-panel/ClustersView.tsx new file mode 100644 index 0000000..37030ab --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/ClustersView.tsx @@ -0,0 +1,170 @@ +import { useMemo } from 'react'; +import Plot from 'react-plotly.js'; +import { useGenesis } from '../../context/GenesisContext'; +import './ClustersView.css'; + +export default function ClustersView() { + const { state, selectProgram } = useGenesis(); + const { programs } = state; + + const programsWithEmbeddings = useMemo(() => { + return programs.filter( + (p) => + p.correct && + p.embedding_pca_2d && + p.embedding_pca_2d.length === 2 && + p.embedding_pca_3d && + p.embedding_pca_3d.length === 3 && + p.combined_score !== null + ); + }, [programs]); + + const plotData = useMemo(() => { + if (programsWithEmbeddings.length === 0) return null; + + const scores = programsWithEmbeddings.map((p) => p.combined_score as number); + const minScore = Math.min(...scores); + const maxScore = Math.max(...scores); + + // Size scale + const getSize = (score: number) => { + if (maxScore === minScore) return 10; + return 5 + 15 * (score - minScore) / (maxScore - minScore); + }; + + const sizes = programsWithEmbeddings.map(p => getSize(p.combined_score as number)); + const bestProgram = programsWithEmbeddings.reduce((best, curr) => + (curr.combined_score! > best.combined_score!) ? curr : best + , programsWithEmbeddings[0]); + + const symbols = programsWithEmbeddings.map(p => p.id === bestProgram.id ? 'star' : 'circle'); + const symbols3d = programsWithEmbeddings.map(p => p.id === bestProgram.id ? 'diamond' : 'circle'); + const lineColors = programsWithEmbeddings.map(p => p.id === bestProgram.id ? 'gold' : 'white'); + const lineWidths = programsWithEmbeddings.map(p => p.id === bestProgram.id ? 2 : 0.5); + + // Use island_idx or cluster if available + // Legacy code uses p.embedding_cluster_id but my types interface doesn't show it explicitly + // checking types again: it is NOT in types.ts I read earlier. + // But legacy code uses it. I should check if `Program` type has it. + // If not, use island_idx for coloring + const colorData = programsWithEmbeddings.map(p => (p as any).embedding_cluster_id ?? p.island_idx ?? 0); + + return { + x2d: programsWithEmbeddings.map(p => p.embedding_pca_2d![0]), + y2d: programsWithEmbeddings.map(p => p.embedding_pca_2d![1]), + x3d: programsWithEmbeddings.map(p => p.embedding_pca_3d![0]), + y3d: programsWithEmbeddings.map(p => p.embedding_pca_3d![1]), + z3d: programsWithEmbeddings.map(p => p.embedding_pca_3d![2]), + ids: programsWithEmbeddings.map(p => p.id), + text: programsWithEmbeddings.map(p => `${p.metadata.patch_name || 'unnamed'}
Score: ${p.combined_score?.toFixed(4)}`), + sizes, + symbols, + symbols3d, + lineColors, + lineWidths, + colorData + }; + }, [programsWithEmbeddings]); + + if (programsWithEmbeddings.length === 0) { + return ( +
+

No PCA embedding data available for this evolution run.

+
+ ); + } + + if (!plotData) return null; + + const handlePlotClick = (event: Readonly) => { + if (event.points && event.points.length > 0) { + const pointIndex = event.points[0].pointIndex; // or pointNumber + // pointIndex corresponds to the index in the data arrays + const program = programsWithEmbeddings[pointIndex]; + if (program) { + selectProgram(program); + } + } + }; + + return ( +
+

PCA Embeddings

+
+
+ + +
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/EmbeddingsView.css b/genesis/webui/frontend/src/components/left-panel/EmbeddingsView.css new file mode 100644 index 0000000..fa13288 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/EmbeddingsView.css @@ -0,0 +1,33 @@ +.embeddings-view { + height: 100%; + display: flex; + flex-direction: column; +} + +.embeddings-view.empty { + align-items: center; + justify-content: center; + color: #666; +} + +.embeddings-controls { + padding: 10px; + background-color: #f8f9fa; + border-bottom: 1px solid #ddd; +} + +.embeddings-controls h4 { + margin: 0 0 10px 0; +} + +.info-text { + color: #666; + font-size: 13px; + margin: 0; +} + +.embeddings-content { + flex: 1; + padding: 20px; + overflow: auto; +} diff --git a/genesis/webui/frontend/src/components/left-panel/EmbeddingsView.tsx b/genesis/webui/frontend/src/components/left-panel/EmbeddingsView.tsx new file mode 100644 index 0000000..20ef77a --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/EmbeddingsView.tsx @@ -0,0 +1,333 @@ +import { useEffect, useRef, useState, useMemo } from 'react'; +import * as d3 from 'd3'; +import { useGenesis } from '../../context/GenesisContext'; +import type { Program } from '../../types'; +import './EmbeddingsView.css'; + +function cosineSimilarity(vecA: number[], vecB: number[]): number { + let dotProduct = 0; + let normA = 0; + let normB = 0; + for (let i = 0; i < vecA.length; i++) { + dotProduct += vecA[i] * vecB[i]; + normA += vecA[i] * vecA[i]; + normB += vecB[i] * vecB[i]; + } + if (normA === 0 || normB === 0) return 0; + return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); +} + +function computeSimilarityMatrix(embeddings: number[][]): number[][] { + const n = embeddings.length; + const matrix = Array(n) + .fill(0) + .map(() => Array(n).fill(0)); + for (let i = 0; i < n; i++) { + for (let j = i; j < n; j++) { + const sim = cosineSimilarity(embeddings[i], embeddings[j]); + matrix[i][j] = sim; + matrix[j][i] = sim; + } + } + return matrix; +} + +export default function EmbeddingsView() { + const { state, selectProgram } = useGenesis(); + const { programs } = state; + const containerRef = useRef(null); + + // Controls state + const [sortBy, setSortBy] = useState<'chronological' | 'performance'>('chronological'); + const [colorScaleName, setColorScaleName] = useState('magma'); + const [cellSize, setCellSize] = useState(8); + const [minVal, setMinVal] = useState(0.5); + const [maxVal, setMaxVal] = useState(1.0); + + // Filter programs with embeddings + const dataWithEmbeddings = useMemo(() => { + // Deduplicate generation 0 programs (logic from legacy) + let filtered = [...programs]; + const gen0Programs = programs.filter(d => d.generation === 0); + if (gen0Programs.length > 1) { + const gen0Groups: Record = {}; + gen0Programs.forEach(prog => { + const key = prog.code || 'no-code'; + if (!gen0Groups[key]) gen0Groups[key] = []; + gen0Groups[key].push(prog); + }); + const duplicateIds = new Set(); + Object.values(gen0Groups).forEach(group => { + if (group.length > 1) { + group.sort((a, b) => (a.island_idx || 0) - (b.island_idx || 0)); + group.slice(1).forEach(prog => duplicateIds.add(prog.id)); + } + }); + filtered = programs.filter(d => !duplicateIds.has(d.id)); + } + return filtered.filter(p => p.embedding && p.embedding.length > 0); + }, [programs]); + + useEffect(() => { + if (!containerRef.current || dataWithEmbeddings.length === 0) return; + + const container = d3.select(containerRef.current); + container.selectAll('*').remove(); + + const embeddings = dataWithEmbeddings.map(d => d.embedding!); + const similarityMatrix = computeSimilarityMatrix(embeddings); + + // Sorting + let ordering = Array.from({ length: dataWithEmbeddings.length }, (_, i) => i); + if (sortBy === 'performance') { + const indexed = dataWithEmbeddings.map((p, i) => ({ p, i })); + indexed.sort((a, b) => { + const scoreA = (a.p.correct && a.p.combined_score != null) ? a.p.combined_score : -Infinity; + const scoreB = (b.p.correct && b.p.combined_score != null) ? b.p.combined_score : -Infinity; + return scoreB - scoreA; + }); + ordering = indexed.map(x => x.i); + } + + // Prepare ordered data + const orderedMatrix = ordering.map(i => ordering.map(j => similarityMatrix[i][j])); + const orderedPrograms = ordering.map(i => dataWithEmbeddings[i]); + + // Visualization parameters + const margin = { top: 50, right: 50, bottom: 50, left: 50 }; + const width = orderedMatrix.length * cellSize; + const height = orderedMatrix.length * cellSize; + const totalWidth = width + margin.left + margin.right + 50; // +50 for score bar + const totalHeight = height + margin.top + margin.bottom; + + const svg = container.append('svg') + .attr('width', totalWidth) + .attr('height', totalHeight); + + const g = svg.append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + + // Color scale + let interpolator; + switch (colorScaleName) { + case 'viridis': interpolator = d3.interpolateViridis; break; + case 'plasma': interpolator = d3.interpolatePlasma; break; + case 'inferno': interpolator = d3.interpolateInferno; break; + default: interpolator = d3.interpolateMagma; + } + const colorScale = d3.scaleSequential(interpolator).domain([minVal, maxVal]); + + // Draw cells + const rows = g.selectAll('.row') + .data(orderedMatrix) + .enter().append('g') + .attr('transform', (d, i) => `translate(0,${i * cellSize})`); + + rows.selectAll('.cell') + .data(d => d) + .enter().append('rect') + .attr('class', 'cell') + .attr('x', (d, i) => i * cellSize) + .attr('width', cellSize) + .attr('height', cellSize) + .style('fill', d => colorScale(d)) + .on('mouseover', (event, d) => { + // Tooltip implementation omitted for brevity, relying on React state could be better but D3 tooltip is standard + // We can add a simple title for now + d3.select(event.currentTarget).append('title').text(`Similarity: ${d.toFixed(3)}`); + }) + .on('click', (event, d) => { + // Find indices + // This is tricky in D3 click handler without data access + // Simplifying: just select if possible. + // Actually we need the index. + // Let's use React state for selection if we want robust interaction + }); + + // Add click handlers with proper index access + rows.each(function(rowData, i) { + d3.select(this).selectAll('.cell').on('click', (event, d) => { + // Finding j index + const j = Math.floor((event.offsetX - margin.left) / cellSize); // approximate + // Better way: capture i and j in data binding + }); + }); + + // Re-implementing cells with indices for click handling + g.selectAll('*').remove(); // Clear first + + orderedMatrix.forEach((row, i) => { + row.forEach((val, j) => { + g.append('rect') + .attr('x', j * cellSize) + .attr('y', i * cellSize) + .attr('width', cellSize) + .attr('height', cellSize) + .style('fill', colorScale(val)) + .style('stroke', 'none') + .on('mouseover', function() { + d3.select(this).style('stroke', '#fff').style('stroke-width', 1); + const progA = orderedPrograms[i]; + const progB = orderedPrograms[j]; + const tooltip = d3.select('.embeddings-tooltip'); + tooltip.style('opacity', 1) + .html(` + ${progA.metadata.patch_name} vs ${progB.metadata.patch_name}
+ Gen ${progA.generation} vs Gen ${progB.generation}
+ Similarity: ${val.toFixed(3)} + `) + .style('left', (event.pageX + 10) + 'px') + .style('top', (event.pageY - 10) + 'px'); + }) + .on('mouseout', function() { + d3.select(this).style('stroke', 'none'); + d3.select('.embeddings-tooltip').style('opacity', 0); + }) + .on('click', () => { + selectProgram(orderedPrograms[i]); // Select the row program + }); + }); + }); + + // Performance bar + const scoreScale = d3.scaleSequential(d3.interpolatePlasma).domain([0, 1]); // Assuming normalized scores or use min/max + const scores = orderedPrograms.map(p => p.combined_score); + const minScore = Math.min(...scores.filter(s => s !== null) as number[]) || 0; + const maxScore = Math.max(...scores.filter(s => s !== null) as number[]) || 1; + scoreScale.domain([minScore, maxScore]); + + const perfBarX = width + 10; + orderedPrograms.forEach((p, i) => { + g.append('rect') + .attr('x', perfBarX) + .attr('y', i * cellSize) + .attr('width', 15) + .attr('height', cellSize) + .style('fill', p.correct && p.combined_score !== null ? scoreScale(p.combined_score) : '#ccc') + .append('title').text(`Score: ${p.combined_score?.toFixed(4) ?? 'N/A'}`); + }); + + g.append('text') + .attr('x', perfBarX + 7.5) + .attr('y', -10) + .style('text-anchor', 'middle') + .style('font-size', '10px') + .text('Score'); + + // Island Subplots + // Filter islands with > 1 programs + const islands = [...new Set(dataWithEmbeddings.map(d => d.island_idx).filter(idx => idx !== null))].sort((a, b) => (a || 0) - (b || 0)); + const validIslands = islands.filter(islandId => + dataWithEmbeddings.filter(d => d.island_idx === islandId).length > 1 + ); + + if (validIslands.length > 0) { + const subplotsContainer = container.append('div') + .attr('class', 'subplots-container') + .style('margin-top', '30px') + .style('display', 'flex') + .style('flex-wrap', 'wrap') + .style('gap', '20px') + .style('justify-content', 'center'); + + container.append('h4').text('Individual Islands - Embedding Similarity Matrices').style('margin-top', '20px').style('text-align', 'center'); + + validIslands.forEach(islandId => { + const islandData = dataWithEmbeddings.filter(d => d.island_idx === islandId); + const islandEmbeddings = islandData.map(d => d.embedding!); + const islandMatrix = computeSimilarityMatrix(islandEmbeddings); + + // Sort island data (default chrono) + const islandOrdering = Array.from({length: islandData.length}, (_, i) => i); + + const subCellSize = Math.min(cellSize, 6); + const subWidth = islandData.length * subCellSize; + const subHeight = islandData.length * subCellSize; + + const subplotDiv = subplotsContainer.append('div') + .style('border', '1px solid #ddd') + .style('padding', '10px') + .style('background', '#fff'); + + subplotDiv.append('h5').text(`Island ${islandId} (${islandData.length})`).style('margin', '0 0 10px 0').style('text-align', 'center'); + + const subSvg = subplotDiv.append('svg') + .attr('width', subWidth + 20) // minimal margin + .attr('height', subHeight + 20); + + const subG = subSvg.append('g').attr('transform', 'translate(10,10)'); + + islandMatrix.forEach((row, i) => { + row.forEach((val, j) => { + subG.append('rect') + .attr('x', j * subCellSize) + .attr('y', i * subCellSize) + .attr('width', subCellSize) + .attr('height', subCellSize) + .style('fill', colorScale(val)) + .style('stroke', 'none') + .on('mouseover', function() { + const progA = islandData[i]; + const progB = islandData[j]; + const tooltip = d3.select('.embeddings-tooltip'); + tooltip.style('opacity', 1) + .html(` + Island ${islandId}
+ ${progA.metadata.patch_name} vs ${progB.metadata.patch_name}
+ Similarity: ${val.toFixed(3)} + `) + .style('left', (event.pageX + 10) + 'px') + .style('top', (event.pageY - 10) + 'px'); + }) + .on('mouseout', () => d3.select('.embeddings-tooltip').style('opacity', 0)) + .on('click', () => selectProgram(islandData[i])); + }); + }); + }); + } + + }, [dataWithEmbeddings, sortBy, colorScaleName, cellSize, minVal, maxVal, selectProgram]); + + if (dataWithEmbeddings.length === 0) { + return ( +
+

No embedding data available for this evolution run.

+
+ ); + } + + return ( +
+
+
+ + + +
+
+ + +
+
+
+
+
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/IslandsView.css b/genesis/webui/frontend/src/components/left-panel/IslandsView.css new file mode 100644 index 0000000..ef9434a --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/IslandsView.css @@ -0,0 +1,15 @@ +.islands-view { + height: 100%; + padding: 20px; +} + +.islands-view.empty { + display: flex; + align-items: center; + justify-content: center; + color: #666; +} + +.islands-view .chart-container { + height: calc(100% - 40px); +} diff --git a/genesis/webui/frontend/src/components/left-panel/IslandsView.tsx b/genesis/webui/frontend/src/components/left-panel/IslandsView.tsx new file mode 100644 index 0000000..ec98d2e --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/IslandsView.tsx @@ -0,0 +1,125 @@ +import { useMemo } from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +import { useGenesis } from '../../context/GenesisContext'; +import './IslandsView.css'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +); + +const ISLAND_COLORS = [ + '#1f77b4', + '#2ca02c', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf', + '#aec7e8', +]; + +export default function IslandsView() { + const { state } = useGenesis(); + const { programs } = state; + + const chartData = useMemo(() => { + // Get unique islands + const islands = [ + ...new Set( + programs + .filter((p) => p.island_idx !== null && p.island_idx !== undefined) + .map((p) => p.island_idx as number) + ), + ].sort((a, b) => a - b); + + if (islands.length === 0) return null; + + // Group by generation and island + const generations = [...new Set(programs.map((p) => p.generation))].sort( + (a, b) => a - b + ); + + const datasets = islands.map((island, idx) => { + const data = generations.map((gen) => { + const islandPrograms = programs.filter( + (p) => + p.generation === gen && + p.island_idx === island && + p.correct && + p.combined_score !== null + ); + if (islandPrograms.length === 0) return null; + return Math.max(...islandPrograms.map((p) => p.combined_score as number)); + }); + + return { + label: `Island ${island}`, + data, + borderColor: ISLAND_COLORS[idx % ISLAND_COLORS.length], + backgroundColor: ISLAND_COLORS[idx % ISLAND_COLORS.length] + '33', + tension: 0.1, + spanGaps: true, + }; + }); + + return { + labels: generations.map((g) => `Gen ${g}`), + datasets, + }; + }, [programs]); + + if (!chartData) { + return ( +
+

No island data available for this evolution run.

+
+ ); + } + + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top' as const, + }, + title: { + display: true, + text: 'Best Score per Island over Generations', + }, + }, + scales: { + y: { + title: { + display: true, + text: 'Best Score', + }, + }, + }, + }; + + return ( +
+
+ +
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/LeftPanel.css b/genesis/webui/frontend/src/components/left-panel/LeftPanel.css new file mode 100644 index 0000000..128bc02 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/LeftPanel.css @@ -0,0 +1,76 @@ +.left-panel-content { + display: flex; + flex-direction: column; + height: 100%; + padding: 15px 20px; +} + +.panel-header { + flex-shrink: 0; +} + +.panel-title { + font-size: 20px; + font-weight: 600; + color: #2c3e50; + margin: 0 0 10px 0; + padding-bottom: 10px; + border-bottom: 2px solid #3498db; + text-align: center; +} + +.panel-title .highlight { + color: #3498db; + font-weight: 700; +} + +.left-tabs { + display: flex; + flex-wrap: wrap; + border-bottom: 1px solid #ddd; + margin-top: 10px; + flex-shrink: 0; + gap: 2px; +} + +.left-tab { + padding: 8px 12px; + cursor: pointer; + border: 1px solid transparent; + border-bottom: none; + background-color: #f1f1f1; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + font-size: 13px; + color: #333; + transition: background-color 0.2s; +} + +.left-tab:hover { + background-color: #e9ecef; +} + +.left-tab.active { + background-color: #fff; + border-color: #ddd; + border-bottom-color: #fff; + margin-bottom: -1px; + font-weight: bold; + color: #2c3e50; +} + +.left-tab-content { + flex: 1; + overflow: hidden; + position: relative; + margin-top: 10px; +} + +.empty-state { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; + font-size: 14px; +} diff --git a/genesis/webui/frontend/src/components/left-panel/LeftPanel.tsx b/genesis/webui/frontend/src/components/left-panel/LeftPanel.tsx new file mode 100644 index 0000000..4f005f9 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/LeftPanel.tsx @@ -0,0 +1,82 @@ +import { useGenesis } from '../../context/GenesisContext'; +import TreeView from './TreeView'; +import ProgramsTable from './ProgramsTable'; +import MetricsView from './MetricsView'; +import EmbeddingsView from './EmbeddingsView'; +import ClustersView from './ClustersView'; +import IslandsView from './IslandsView'; +import ModelPosteriorsView from './ModelPosteriorsView'; +import BestPathView from './BestPathView'; +import './LeftPanel.css'; + +const LEFT_TABS = [ + { id: 'tree-view', label: 'Tree' }, + { id: 'table-view', label: 'Programs' }, + { id: 'metrics-view', label: 'Metrics' }, + { id: 'embeddings-view', label: 'Embeddings' }, + { id: 'clusters-view', label: 'Clusters' }, + { id: 'islands-view', label: 'Islands' }, + { id: 'model-posteriors-view', label: 'LLM Posterior' }, + { id: 'best-path-view', label: 'Path → Best' }, +]; + +export default function LeftPanel() { + const { state, setLeftTab } = useGenesis(); + const { selectedLeftTab, programs } = state; + + const renderContent = () => { + if (programs.length === 0) { + return ( +
+

Select a database to view evolution results.

+
+ ); + } + + switch (selectedLeftTab) { + case 'tree-view': + return ; + case 'table-view': + return ; + case 'metrics-view': + return ; + case 'embeddings-view': + return ; + case 'clusters-view': + return ; + case 'islands-view': + return ; + case 'model-posteriors-view': + return ; + case 'best-path-view': + return ; + default: + return ; + } + }; + + return ( +
+
+

+ 🎏 Genesis: Open-Ended Program + Evolution 🎏 +

+
+ +
+ {LEFT_TABS.map((tab) => ( + + ))} +
+ +
{renderContent()}
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/MetricsView.css b/genesis/webui/frontend/src/components/left-panel/MetricsView.css new file mode 100644 index 0000000..4097814 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/MetricsView.css @@ -0,0 +1,36 @@ +.metrics-view { + padding: 20px; + height: 100%; + overflow-y: auto; +} + +.metrics-view.empty { + display: flex; + align-items: center; + justify-content: center; + color: #666; +} + +.metrics-view h4 { + margin: 0 0 20px 0; + text-align: center; + color: #2c3e50; +} + +.chart-container { + margin-bottom: 30px; +} + +.chart-container h5 { + margin: 0 0 10px 0; + color: #495057; + font-size: 14px; +} + +.chart-wrapper { + height: 250px; + background: #fff; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 10px; +} diff --git a/genesis/webui/frontend/src/components/left-panel/MetricsView.tsx b/genesis/webui/frontend/src/components/left-panel/MetricsView.tsx new file mode 100644 index 0000000..11cd3b9 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/MetricsView.tsx @@ -0,0 +1,152 @@ +import { useMemo } from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +import { useGenesis } from '../../context/GenesisContext'; +import './MetricsView.css'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +); + +export default function MetricsView() { + const { state } = useGenesis(); + const { programs } = state; + + const metricsData = useMemo(() => { + if (programs.length === 0) return null; + + // Group by generation + const byGeneration = new Map(); + programs.forEach((p) => { + if (!byGeneration.has(p.generation)) { + byGeneration.set(p.generation, []); + } + byGeneration.get(p.generation)!.push(p); + }); + + const generations = [...byGeneration.keys()].sort((a, b) => a - b); + const maxScores: number[] = []; + const avgScores: number[] = []; + const cumulativeCosts: number[] = []; + let runningCost = 0; + + generations.forEach((gen) => { + const genPrograms = byGeneration.get(gen)!; + const correctPrograms = genPrograms.filter( + (p) => p.correct && p.combined_score !== null + ); + + if (correctPrograms.length > 0) { + const scores = correctPrograms.map((p) => p.combined_score as number); + maxScores.push(Math.max(...scores)); + avgScores.push(scores.reduce((a, b) => a + b, 0) / scores.length); + } else { + maxScores.push(maxScores[maxScores.length - 1] || 0); + avgScores.push(avgScores[avgScores.length - 1] || 0); + } + + genPrograms.forEach((p) => { + runningCost += + (p.metadata.api_cost || 0) + + (p.metadata.embed_cost || 0) + + (p.metadata.novelty_cost || 0) + + (p.metadata.meta_cost || 0); + }); + cumulativeCosts.push(runningCost); + }); + + return { generations, maxScores, avgScores, cumulativeCosts }; + }, [programs]); + + if (!metricsData) { + return ( +
+

No data available

+
+ ); + } + + const scoreChartData = { + labels: metricsData.generations.map((g) => `Gen ${g}`), + datasets: [ + { + label: 'Max Score', + data: metricsData.maxScores, + borderColor: '#2ecc71', + backgroundColor: 'rgba(46, 204, 113, 0.1)', + tension: 0.1, + }, + { + label: 'Avg Score', + data: metricsData.avgScores, + borderColor: '#3498db', + backgroundColor: 'rgba(52, 152, 219, 0.1)', + tension: 0.1, + }, + ], + }; + + const costChartData = { + labels: metricsData.generations.map((g) => `Gen ${g}`), + datasets: [ + { + label: 'Cumulative Cost ($)', + data: metricsData.cumulativeCosts, + borderColor: '#e74c3c', + backgroundColor: 'rgba(231, 76, 60, 0.1)', + fill: true, + tension: 0.1, + }, + ], + }; + + const chartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top' as const, + }, + }, + scales: { + y: { + beginAtZero: false, + }, + }, + }; + + return ( +
+

Metrics Over Generations

+ +
+
Performance Score
+
+ +
+
+ +
+
Cumulative Cost
+
+ +
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.css b/genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.css new file mode 100644 index 0000000..c0875d6 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.css @@ -0,0 +1,50 @@ +.model-posteriors-view { + height: 100%; + padding: 20px; + overflow-y: auto; +} + +.model-posteriors-view.empty { + display: flex; + align-items: center; + justify-content: center; + color: #666; +} + +.model-posteriors-view h4 { + margin: 0 0 20px 0; + text-align: center; +} + +.model-stats { + margin-top: 20px; + padding: 15px; + background-color: #f8f9fa; + border-radius: 5px; +} + +.model-stats h5 { + margin: 0 0 10px 0; +} + +.stats-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; +} + +.stats-table th, +.stats-table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} + +.stats-table th { + background-color: #e9ecef; + font-weight: 600; +} + +.stats-table tbody tr:hover { + background-color: #f1f8ff; +} diff --git a/genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.tsx b/genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.tsx new file mode 100644 index 0000000..08fd34d --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/ModelPosteriorsView.tsx @@ -0,0 +1,257 @@ +import { useEffect, useRef, useMemo } from 'react'; +import * as d3 from 'd3'; +import { useGenesis } from '../../context/GenesisContext'; +import type { Program } from '../../types'; +import './ModelPosteriorsView.css'; + +export default function ModelPosteriorsView() { + const { state } = useGenesis(); + const { programs } = state; + const posteriorsRef = useRef(null); + const countsRef = useRef(null); + + // Extract data logic + const data = useMemo(() => { + // Find programs with posteriors + const programsWithPosteriors = programs.filter(d => + (d.metadata?.llm_result?.model_posteriors && typeof d.metadata.llm_result.model_posteriors === 'object') || + (d.metadata?.model_posteriors && typeof d.metadata.model_posteriors === 'object') + ); + + if (programsWithPosteriors.length === 0) return null; + + const getPosteriors = (p: Program) => { + return (p.metadata?.llm_result?.model_posteriors as Record) || + (p.metadata?.model_posteriors as Record) || + {}; + }; + + const allModels = new Set(); + programsWithPosteriors.forEach(p => { + Object.keys(getPosteriors(p)).forEach(m => allModels.add(m)); + }); + const models = Array.from(allModels).sort(); + + const generationData: Record = {}; + programsWithPosteriors.forEach(p => { + if (!generationData[p.generation]) generationData[p.generation] = []; + generationData[p.generation].push(p); + }); + + const generations = Object.keys(generationData).map(Number).sort((a, b) => a - b); + + const chartData: any[] = []; + generations.forEach(gen => { + const genPrograms = generationData[gen]; + models.forEach(model => { + const programsWithThisModel = genPrograms.filter(p => { + const posteriors = getPosteriors(p); + return posteriors[model] !== undefined; + }); + const modelPosteriors = programsWithThisModel.map(p => getPosteriors(p)[model]); + + if (modelPosteriors.length > 0) { + const avgPosterior = modelPosteriors.reduce((a, b) => a + b, 0) / modelPosteriors.length; + + // Count programs generated by this model + const count = genPrograms.filter(p => { + const modelName = p.metadata.model || p.metadata.model_name || p.metadata.llm_result?.model; + return modelName === model; + }).length; + + chartData.push({ + generation: gen, + model, + posterior: avgPosterior, + count + }); + } + }); + }); + + return { models, generations, chartData, programsWithPosteriors }; + }, [programs]); + + useEffect(() => { + if (!data || !posteriorsRef.current || !countsRef.current) return; + + const { models, generations, chartData } = data; + + // Clear + d3.select(posteriorsRef.current).selectAll('*').remove(); + d3.select(countsRef.current).selectAll('*').remove(); + + const margin = { top: 20, right: 80, bottom: 50, left: 50 }; + const width = posteriorsRef.current.clientWidth - margin.left - margin.right; + const height = 400 - margin.top - margin.bottom; + + const colorScale = d3.scaleOrdinal(d3.schemeCategory10).domain(models); + + // --- Posteriors Chart --- + const svgPost = d3.select(posteriorsRef.current) + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + + const xPost = d3.scaleLinear() + .domain(d3.extent(generations) as [number, number]) + .range([0, width]); + + const yPost = d3.scaleLinear() + .domain([0, 1]) + .range([height, 0]); + + svgPost.append('g') + .attr('transform', `translate(0,${height})`) + .call(d3.axisBottom(xPost).tickFormat(d3.format('d'))); + + svgPost.append('g').call(d3.axisLeft(yPost)); + + // Lines + const line = d3.line() + .x(d => xPost(d.generation)) + .y(d => yPost(d.posterior)) + .curve(d3.curveMonotoneX); + + models.forEach(model => { + const modelData = chartData.filter(d => d.model === model).sort((a, b) => a.generation - b.generation); + if (modelData.length > 0) { + svgPost.append('path') + .datum(modelData) + .attr('fill', 'none') + .attr('stroke', colorScale(model)) + .attr('stroke-width', 2) + .attr('d', line); + + // Dots + svgPost.selectAll(`.dot-${model.replace(/[^a-zA-Z0-9]/g, '')}`) + .data(modelData) + .enter().append('circle') + .attr('cx', d => xPost(d.generation)) + .attr('cy', d => yPost(d.posterior)) + .attr('r', 4) + .attr('fill', colorScale(model)) + .append('title').text(d => `${model}: ${(d.posterior * 100).toFixed(1)}%`); + } + }); + + // Legend + const legend = svgPost.append('g') + .attr('transform', `translate(${width + 10}, 0)`); + + models.forEach((model, i) => { + const g = legend.append('g').attr('transform', `translate(0, ${i * 20})`); + g.append('rect').attr('width', 10).attr('height', 10).attr('fill', colorScale(model)); + g.append('text').attr('x', 15).attr('y', 10).text(model).style('font-size', '10px').attr('alignment-baseline', 'middle'); + }); + + // --- Counts Chart --- + const svgCounts = d3.select(countsRef.current) + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + + // Calculate cumulative counts + const cumulativeData: Record = {}; + models.forEach(model => { + cumulativeData[model] = []; + let sum = 0; + generations.forEach(gen => { + const point = chartData.find(d => d.generation === gen && d.model === model); + const count = point ? point.count : 0; + sum += count; + cumulativeData[model].push({ generation: gen, count: sum }); + }); + }); + + const maxCount = Math.max(...Object.values(cumulativeData).flat().map(d => d.count)); + + const yCounts = d3.scaleLinear() + .domain([0, maxCount]) + .range([height, 0]); + + svgCounts.append('g') + .attr('transform', `translate(0,${height})`) + .call(d3.axisBottom(xPost).tickFormat(d3.format('d'))); + + svgCounts.append('g').call(d3.axisLeft(yCounts)); + + const lineCount = d3.line() + .x(d => xPost(d.generation)) + .y(d => yCounts(d.count)); + + models.forEach(model => { + const modelData = cumulativeData[model]; + svgCounts.append('path') + .datum(modelData) + .attr('fill', 'none') + .attr('stroke', colorScale(model)) + .attr('stroke-width', 2) + .attr('d', lineCount); + }); + + // Legend for counts + const legendCounts = svgCounts.append('g') + .attr('transform', `translate(${width + 10}, 0)`); + + models.forEach((model, i) => { + const g = legendCounts.append('g').attr('transform', `translate(0, ${i * 20})`); + g.append('rect').attr('width', 10).attr('height', 10).attr('fill', colorScale(model)); + g.append('text').attr('x', 15).attr('y', 10).text(model).style('font-size', '10px').attr('alignment-baseline', 'middle'); + }); + + }, [data]); + + if (!data) { + return ( +
+

No model posterior data available for this evolution run.

+
+ ); + } + + return ( +
+

Model Posteriors Over Generations

+ +
Cumulative Programs by Model
+
+ +
Average Model Posterior
+
+ +
+
Model Usage Summary
+ + + + + + + + + + + {data.models.map((model) => { + const modelPrograms = programs.filter(p => (p.metadata.model || p.metadata.model_name || p.metadata.llm_result?.model) === model); + const correct = modelPrograms.filter(p => p.correct).length; + const total = modelPrograms.length; + return ( + + + + + + + ); + })} + +
ModelTotalCorrectSuccess Rate
{model}{total}{correct}{total > 0 ? ((correct / total) * 100).toFixed(1) : 0}%
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/left-panel/ProgramsTable.css b/genesis/webui/frontend/src/components/left-panel/ProgramsTable.css new file mode 100644 index 0000000..1ccf5d4 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/ProgramsTable.css @@ -0,0 +1,90 @@ +.programs-table-container { + display: flex; + flex-direction: column; + height: 100%; +} + +.table-controls { + padding: 10px; + border-bottom: 1px solid #ddd; + background-color: #f8f9fa; + flex-shrink: 0; +} + +.checkbox-label { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + cursor: pointer; +} + +.table-wrapper { + flex: 1; + overflow: auto; +} + +.programs-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +.programs-table th, +.programs-table td { + border: 1px solid #ddd; + padding: 6px 8px; + text-align: left; + white-space: nowrap; +} + +.programs-table th { + background-color: #f8f9fa; + font-weight: 600; + position: sticky; + top: 0; + z-index: 1; + border-bottom: 2px solid #dee2e6; +} + +.programs-table th.sortable { + cursor: pointer; + user-select: none; +} + +.programs-table th.sortable:hover { + background-color: #e9ecef; +} + +.programs-table tbody tr { + cursor: pointer; + transition: background-color 0.15s; +} + +.programs-table tbody tr:hover { + background-color: #f1f8ff; +} + +.programs-table tbody tr.selected { + background-color: #d4eaff !important; +} + +.programs-table tbody tr.incorrect { + background-color: #fff5f5; +} + +.programs-table tbody tr.incorrect:hover { + background-color: #ffe5e5; +} + +.patch-name { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; +} + +.model-cell { + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/genesis/webui/frontend/src/components/left-panel/ProgramsTable.tsx b/genesis/webui/frontend/src/components/left-panel/ProgramsTable.tsx new file mode 100644 index 0000000..bccecaa --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/ProgramsTable.tsx @@ -0,0 +1,156 @@ +import { useState, useMemo } from 'react'; +import { useGenesis } from '../../context/GenesisContext'; +import type { Program, SortState } from '../../types'; +import './ProgramsTable.css'; + +export default function ProgramsTable() { + const { state, selectProgram, setRightTab } = useGenesis(); + const { programs, selectedProgram } = state; + + const [showIncorrect, setShowIncorrect] = useState(false); + const [sortState, setSortState] = useState({ + key: 'generation', + direction: 'asc', + }); + + // Filter and sort programs + const filteredPrograms = useMemo(() => { + let filtered = showIncorrect + ? programs + : programs.filter((p) => p.correct); + + // Sort + filtered = [...filtered].sort((a, b) => { + const aVal = getSortValue(a, sortState.key); + const bVal = getSortValue(b, sortState.key); + + if (aVal === null && bVal === null) return 0; + if (aVal === null) return 1; + if (bVal === null) return -1; + + if (typeof aVal === 'number' && typeof bVal === 'number') { + return sortState.direction === 'asc' ? aVal - bVal : bVal - aVal; + } + + const aStr = String(aVal); + const bStr = String(bVal); + return sortState.direction === 'asc' + ? aStr.localeCompare(bStr) + : bStr.localeCompare(aStr); + }); + + // Add rank + return filtered.map((p, i) => ({ ...p, rank: i + 1 })); + }, [programs, showIncorrect, sortState]); + + const handleSort = (key: string) => { + setSortState((prev) => ({ + key, + direction: prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc', + })); + }; + + const handleRowClick = (program: Program) => { + selectProgram(program); + setRightTab('code-viewer'); + }; + + const getSortArrow = (key: string) => { + if (sortState.key !== key) return ''; + return sortState.direction === 'asc' ? ' ↑' : ' ↓'; + }; + + return ( +
+
+ +
+ +
+ + + + + + + + + + + + + + + + {filteredPrograms.map((program) => ( + handleRowClick(program)} + > + + + + + + + + + + + ))} + +
handleSort('rank')}> + Rank{getSortArrow('rank')} + handleSort('generation')}> + Gen{getSortArrow('generation')} + Patch NameType handleSort('island_idx')} + > + Island{getSortArrow('island_idx')} + handleSort('combined_score')} + > + Score{getSortArrow('combined_score')} + handleSort('api_cost')}> + API Cost{getSortArrow('api_cost')} + handleSort('complexity')}> + Complexity{getSortArrow('complexity')} + Model
{(program as Program & { rank: number }).rank}{program.generation} + {program.metadata.patch_name} + {program.metadata.patch_type}{program.island_idx ?? '-'}{program.combined_score?.toFixed(4) ?? 'N/A'}${(program.metadata.api_cost || 0).toFixed(4)}{program.complexity?.toFixed(2) ?? '-'} + {program.metadata.model || '-'} +
+
+
+ ); +} + +function getSortValue( + program: Program, + key: string +): string | number | null { + switch (key) { + case 'generation': + return program.generation; + case 'combined_score': + return program.combined_score; + case 'island_idx': + return program.island_idx; + case 'api_cost': + return program.metadata.api_cost || 0; + case 'complexity': + return program.complexity; + default: + return null; + } +} diff --git a/genesis/webui/frontend/src/components/left-panel/TreeView.css b/genesis/webui/frontend/src/components/left-panel/TreeView.css new file mode 100644 index 0000000..c4ca730 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/TreeView.css @@ -0,0 +1,98 @@ +.tree-view-container { + width: 100%; + height: 100%; + position: relative; + overflow: hidden; +} + +.tree-view-container svg { + width: 100%; + height: 100%; +} + +.tree-tooltip { + position: fixed; + text-align: left; + padding: 8px; + font-size: 12px; + background: rgba(0, 0, 0, 0.85); + color: #fff; + border-radius: 4px; + pointer-events: none; + line-height: 1.5; + z-index: 1000; + max-width: 280px; + word-wrap: break-word; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3); + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.tree-legend { + position: absolute; + top: 10px; + right: 10px; + padding: 10px; + border: 2px solid #ccc; + border-radius: 5px; + background-color: rgba(248, 249, 250, 0.95); + font-size: 12px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + z-index: 10; +} + +.tree-legend h5 { + margin: 0 0 8px 0; + font-size: 13px; + color: #333; +} + +.legend-section { + display: flex; + flex-direction: column; + gap: 6px; +} + +.legend-item { + display: flex; + align-items: center; + gap: 6px; +} + +.legend-item svg { + flex-shrink: 0; +} + +.node { + cursor: pointer; +} + +.node.selected path { + stroke: #e58e26 !important; + stroke-width: 4 !important; + filter: drop-shadow(0px 3px 8px rgba(229, 130, 38, 0.6)) !important; +} + +.link { + fill: none; + stroke-opacity: 0.8; +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 0.8; + } + 50% { + transform: scale(1.1); + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 0.8; + } +} + +.node.best-node circle:first-child { + animation: pulse 2s infinite; +} diff --git a/genesis/webui/frontend/src/components/left-panel/TreeView.tsx b/genesis/webui/frontend/src/components/left-panel/TreeView.tsx new file mode 100644 index 0000000..ddacd40 --- /dev/null +++ b/genesis/webui/frontend/src/components/left-panel/TreeView.tsx @@ -0,0 +1,407 @@ +import { useEffect, useRef, useCallback } from 'react'; +import * as d3 from 'd3'; +import { useGenesis } from '../../context/GenesisContext'; +import type { Program } from '../../types'; +import './TreeView.css'; + +interface TreeNode extends Program { + isUnifiedRoot?: boolean; + isVirtual?: boolean; + children?: TreeNode[]; +} + +interface D3TreeNode extends d3.HierarchyPointNode {} + +// Shape map for patch types +const SHAPE_MAP: Record = { + init: d3.symbolDiamond, + full: d3.symbolCircle, + diff: d3.symbolSquare, + cross: d3.symbolCross, +}; + +// Island colors (avoiding orange used for best path) +const ISLAND_COLORS = [ + '#1f77b4', + '#2ca02c', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf', + '#aec7e8', +]; + +export default function TreeView() { + const containerRef = useRef(null); + const svgRef = useRef(null); + const { state, selectProgram, stats, setRightTab } = useGenesis(); + const { programs, selectedProgram } = state; + + const renderTree = useCallback(() => { + if (!containerRef.current || programs.length === 0) return; + + // Clear previous SVG + d3.select(containerRef.current).select('svg').remove(); + + const container = containerRef.current; + const width = container.offsetWidth; + const height = container.offsetHeight; + + // Process data to create unified root if needed + let processedData: TreeNode[] = [...programs]; + const gen0Programs = programs.filter((p) => p.generation === 0); + const unifiedRootId = '___unified_root___'; + + if (gen0Programs.length > 1) { + const firstGen0 = gen0Programs[0]; + const unifiedRoot: TreeNode = { + ...firstGen0, + id: unifiedRootId, + parent_id: null, + metadata: { ...firstGen0.metadata, patch_name: 'Initial Program' }, + island_idx: null, + isUnifiedRoot: true, + }; + processedData.push(unifiedRoot); + processedData = processedData.map((d) => { + if (d.generation === 0 && d.id !== unifiedRootId) { + return { ...d, parent_id: unifiedRootId }; + } + return d; + }); + } + + const nodes = processedData.map((d) => ({ + ...d, + agent_name: d.metadata.patch_name || d.agent_name || 'unnamed_agent', + })); + + const nodeMap = new Map(nodes.map((node) => [node.id, node])); + + // Create hierarchy data + let hierarchyData = JSON.parse(JSON.stringify(nodes)); + const rootNodes = hierarchyData.filter( + (n: TreeNode) => !n.parent_id || !nodeMap.has(n.parent_id) + ); + const virtualRootId = '___virtual_root___'; + + const hasUnifiedRoot = rootNodes.some((n: TreeNode) => n.isUnifiedRoot); + if (rootNodes.length > 1 && !hasUnifiedRoot) { + hierarchyData.push({ + id: virtualRootId, + parent_id: '', + agent_name: 'VIRTUAL ROOT', + isVirtual: true, + generation: -1, + } as TreeNode); + rootNodes.forEach((rn: TreeNode) => { + rn.parent_id = virtualRootId; + }); + } + + // Create stratify layout + const root = d3 + .stratify() + .id((d) => d.id) + .parentId((d) => d.parent_id || undefined)(hierarchyData); + + root.sort( + (a, b) => + (a.data.generation ?? 0) - (b.data.generation ?? 0) || + (a.data.timestamp ?? 0) - (b.data.timestamp ?? 0) + ); + + // Create tree layout + const nodeWidth = 100; + const nodeHeight = 200; + const treeLayout = d3.tree().nodeSize([nodeWidth, nodeHeight]); + treeLayout(root); + + // Calculate dimensions + let minX = Infinity, + maxX = -Infinity; + root.each((d) => { + const x = d.x ?? 0; + if (x < minX) minX = x; + if (x > maxX) maxX = x; + }); + const treeWidth = maxX - minX; + const treeHeight = root.height * nodeHeight; + + const margin = { top: 100, right: 120, bottom: 100, left: 120 }; + + // Create SVG + const svg = d3 + .select(container) + .append('svg') + .attr('width', width) + .attr('height', height) + .attr( + 'viewBox', + `${minX - margin.left} 0 ${treeWidth + margin.left + margin.right} ${treeHeight + margin.top + margin.bottom}` + ) + .call( + d3 + .zoom() + .scaleExtent([0.1, 8]) + .on('zoom', (event) => g.attr('transform', event.transform)) + ); + + svgRef.current = svg.node(); + + const g = svg.append('g').attr('transform', `translate(0, ${margin.top})`); + + // Find best program + const bestProgram = stats.bestProgram; + const bestNodeId = bestProgram?.id; + + // Calculate ancestor path for best node + const ancestorIds = new Set(); + if (bestNodeId) { + const descendants = root.descendants() as D3TreeNode[]; + const bestNodeD3 = descendants.find((d) => d.data.id === bestNodeId); + if (bestNodeD3) { + bestNodeD3.ancestors().forEach((ancestor: D3TreeNode) => { + ancestorIds.add(ancestor.data.id); + }); + } + } + + // Color scale for scores + const correctPrograms = programs.filter( + (p) => p.correct && p.combined_score !== null + ); + const scores = correctPrograms.map((p) => p.combined_score as number); + const minScore = scores.length > 0 ? Math.min(...scores) : 0; + const maxScore = scores.length > 0 ? Math.max(...scores) : 1; + const colorScale = d3 + .scaleSequential(d3.interpolateViridis) + .domain([minScore, maxScore]); + + // Island color scale + const islandColorScale = d3.scaleOrdinal(ISLAND_COLORS); + + // Filter visible nodes (exclude virtual) + const visibleLinks = root + .links() + .filter((d) => !(d.source.data as TreeNode).isVirtual); + const visibleNodes = root + .descendants() + .filter((d) => !(d.data as TreeNode).isVirtual); + + // Draw links + g.append('g') + .attr('class', 'links') + .selectAll('path') + .data(visibleLinks) + .enter() + .append('path') + .attr('class', 'link') + .attr('fill', 'none') + .style('stroke', (d) => { + const sourceId = d.source.data.id; + const targetId = d.target.data.id; + if (ancestorIds.has(sourceId) && ancestorIds.has(targetId)) { + return '#ff8c00'; + } + return '#999'; + }) + .style('stroke-width', (d) => { + const sourceId = d.source.data.id; + const targetId = d.target.data.id; + if (ancestorIds.has(sourceId) && ancestorIds.has(targetId)) { + return 4; + } + return 1.5; + }) + .attr('d', (d) => { + const sourceX = (d.source as D3TreeNode).x ?? 0; + const sourceY = (d.source as D3TreeNode).y ?? 0; + const targetX = (d.target as D3TreeNode).x ?? 0; + const targetY = (d.target as D3TreeNode).y ?? 0; + return `M${sourceX},${sourceY}C${sourceX},${(sourceY + targetY) / 2} ${targetX},${(sourceY + targetY) / 2} ${targetX},${targetY}`; + }); + + // Symbol generator + const symbol = d3.symbol().size(2500); + const getShape = (patchType: string) => + SHAPE_MAP[patchType] || d3.symbolCircle; + + // Draw nodes + const node = g + .append('g') + .attr('class', 'nodes') + .selectAll('g') + .data(visibleNodes) + .enter() + .append('g') + .attr( + 'class', + (d) => + `node ${d.data.id === bestNodeId ? 'best-node' : ''} ${d.data.id === selectedProgram?.id ? 'selected' : ''}` + ) + .attr('transform', (d) => `translate(${d.x ?? 0},${d.y ?? 0})`) + .style('cursor', 'pointer') + .on('click', (_, d) => { + const originalProgram = programs.find((p) => p.id === d.data.id); + if (originalProgram) { + selectProgram(originalProgram); + setRightTab('code-viewer'); + } + }); + + // Draw node shapes + node + .append('path') + .attr('d', (d) => { + if (d.data.isUnifiedRoot) { + symbol.type(d3.symbolStar).size(750); + } else { + symbol.type(getShape(d.data.metadata?.patch_type || 'full')); + } + return symbol() || ''; + }) + .style('fill', (d) => { + if (d.data.isUnifiedRoot) return '#9b59b6'; + if (d.data.id === bestNodeId) return '#ffd700'; + if (!d.data.correct) return '#e74c3c'; + const score = d.data.combined_score; + if (score !== null && !isNaN(score)) { + return colorScale(score); + } + return '#3498db'; + }) + .style('stroke', (d) => { + if (ancestorIds.has(d.data.id) && d.data.correct) { + return '#ff8c00'; + } + return '#000'; + }) + .style('stroke-width', (d) => (ancestorIds.has(d.data.id) ? 4 : 3)) + .style('filter', (d) => + d.data.id === bestNodeId + ? 'drop-shadow(0px 3px 6px rgba(255, 140, 0, 0.5))' + : 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))' + ); + + // Add pulse ring for best node + node + .filter((d) => d.data.id === bestNodeId) + .insert('circle', ':first-child') + .attr('r', 24) + .style('fill', 'none') + .style('stroke', '#ff8c00') + .style('stroke-width', 3) + .style('stroke-dasharray', '5,3') + .style('opacity', 0.8); + + // Add generation text + node + .append('text') + .attr('dy', '0.75em') + .attr('text-anchor', 'middle') + .style('font-size', '24px') + .style('font-weight', 'bold') + .style('fill', 'white') + .style('pointer-events', 'none') + .text((d) => d.data.generation); + + // Add island indicators + const islandNodes = node.filter( + (d) => d.data.island_idx !== null && d.data.island_idx !== undefined + ); + + islandNodes + .append('rect') + .attr('x', -15) + .attr('y', -35) + .attr('width', 30) + .attr('height', 20) + .attr('rx', 4) + .style('fill', (d) => islandColorScale(String(d.data.island_idx))) + .style('stroke', '#2c3e50') + .style('stroke-width', '1px'); + + islandNodes + .append('text') + .attr('x', 0) + .attr('y', -15) + .attr('text-anchor', 'middle') + .attr('dy', '0.35em') + .style('font-size', '17px') + .style('font-weight', 'bold') + .style('fill', 'white') + .style('pointer-events', 'none') + .text((d) => `I${d.data.island_idx}`); + + // Add tooltips + node + .on('mouseover', (_, d) => { + const tooltip = d3.select('.tree-tooltip'); + tooltip.style('opacity', 1).html(` + ${d.data.metadata?.patch_name || 'Unknown'}
+ Score: ${d.data.combined_score?.toFixed(4) || 'N/A'}
+ Type: ${d.data.metadata?.patch_type || 'N/A'}
+ Island: ${d.data.island_idx ?? 'N/A'} + `); + }) + .on('mousemove', (event) => { + d3.select('.tree-tooltip') + .style('left', event.pageX + 15 + 'px') + .style('top', event.pageY - 10 + 'px'); + }) + .on('mouseout', () => { + d3.select('.tree-tooltip').style('opacity', 0); + }); + }, [programs, selectedProgram, stats.bestProgram, selectProgram, setRightTab]); + + useEffect(() => { + renderTree(); + + // Handle window resize + const handleResize = () => { + renderTree(); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [renderTree]); + + return ( +
+
+
+
+
Nodes
+
+ + + + Best +
+
+ + + + Error +
+
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.css b/genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.css new file mode 100644 index 0000000..a241101 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.css @@ -0,0 +1,80 @@ +.code-viewer-panel { + height: 100%; + display: flex; + flex-direction: column; +} + +.code-viewer-panel.empty { + align-items: center; + justify-content: center; + color: #666; +} + +.code-controls { + display: flex; + gap: 8px; + margin-bottom: 10px; + align-items: center; +} + +.code-controls button { + padding: 6px 12px; + border: 1px solid #ddd; + border-radius: 4px; + background: #f8f9fa; + cursor: pointer; + font-size: 13px; + transition: background-color 0.2s; +} + +.code-controls button:hover { + background: #e9ecef; +} + +.language-badge { + margin-left: auto; + padding: 4px 8px; + background: #e9ecef; + border-radius: 3px; + font-size: 12px; + color: #495057; + text-transform: uppercase; +} + +.code-container { + flex: 1; + display: flex; + overflow: auto; + background: #f6f8fa; + border: 1px solid #e9ecef; + border-radius: 4px; +} + +.line-numbers { + padding: 12px 10px; + text-align: right; + color: rgba(0, 0, 0, 0.3); + background: #f0f0f0; + border-right: 1px solid #ddd; + user-select: none; + font-family: 'Consolas', 'Monaco', 'Andale Mono', monospace; + font-size: 12px; + line-height: 1.5; +} + +.line-numbers span { + display: block; +} + +.code-container pre { + margin: 0; + padding: 12px; + flex: 1; + overflow: auto; +} + +.code-container code { + font-family: 'Consolas', 'Monaco', 'Andale Mono', monospace; + font-size: 12px; + line-height: 1.5; +} diff --git a/genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.tsx b/genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.tsx new file mode 100644 index 0000000..7d5e808 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/CodeViewerPanel.tsx @@ -0,0 +1,98 @@ +import { useEffect, useRef, useCallback } from 'react'; +import hljs from 'highlight.js/lib/core'; +import python from 'highlight.js/lib/languages/python'; +import rust from 'highlight.js/lib/languages/rust'; +import cpp from 'highlight.js/lib/languages/cpp'; +import javascript from 'highlight.js/lib/languages/javascript'; +import 'highlight.js/styles/github.css'; +import { useGenesis } from '../../context/GenesisContext'; +import './CodeViewerPanel.css'; + +// Register languages +hljs.registerLanguage('python', python); +hljs.registerLanguage('rust', rust); +hljs.registerLanguage('cpp', cpp); +hljs.registerLanguage('javascript', javascript); + +export default function CodeViewerPanel() { + const { state } = useGenesis(); + const { selectedProgram } = state; + const codeRef = useRef(null); + + useEffect(() => { + if (codeRef.current && selectedProgram?.code) { + hljs.highlightElement(codeRef.current); + } + }, [selectedProgram?.code]); + + const handleCopy = useCallback(async () => { + if (selectedProgram?.code) { + try { + await navigator.clipboard.writeText(selectedProgram.code); + // Could add a toast notification here + } catch (err) { + console.error('Failed to copy:', err); + } + } + }, [selectedProgram?.code]); + + const handleDownload = useCallback(() => { + if (selectedProgram?.code) { + const language = selectedProgram.language || 'py'; + const extension = + { + python: 'py', + rust: 'rs', + cpp: 'cpp', + javascript: 'js', + }[language] || language; + + const filename = `${selectedProgram.metadata.patch_name || 'code'}_gen${selectedProgram.generation}.${extension}`; + const blob = new Blob([selectedProgram.code], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); + } + }, [selectedProgram]); + + if (!selectedProgram) { + return ( +
+

Select a node from the tree to view code.

+
+ ); + } + + const language = selectedProgram.language || 'python'; + const lines = selectedProgram.code.split('\n'); + + return ( +
+
+ + + {language} +
+ +
+
+ {lines.map((_, i) => ( + {i + 1} + ))} +
+
+          
+            {selectedProgram.code}
+          
+        
+
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.css b/genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.css new file mode 100644 index 0000000..9ace6ac --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.css @@ -0,0 +1,107 @@ +.diff-viewer-panel { + height: 100%; + display: flex; + flex-direction: column; +} + +.diff-viewer-panel.empty { + align-items: center; + justify-content: center; + color: #666; +} + +.diff-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.diff-header h4 { + margin: 0; + color: #2c3e50; +} + +.diff-stats { + display: flex; + gap: 15px; +} + +.diff-stats .stat { + font-size: 13px; + font-weight: 600; +} + +.diff-stats .additions { + color: #22863a; +} + +.diff-stats .deletions { + color: #cb2431; +} + +.diff-info { + display: flex; + gap: 10px; + align-items: center; + padding: 10px; + background: #f6f8fa; + border: 1px solid #e1e4e8; + border-radius: 4px; + margin-bottom: 10px; + font-size: 13px; +} + +.diff-content { + flex: 1; + overflow: auto; + background: #fff; + border: 1px solid #e1e4e8; + border-radius: 4px; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, + monospace; + font-size: 12px; +} + +.diff-line { + display: flex; + line-height: 1.5; +} + +.diff-line.added { + background-color: #e6ffed; +} + +.diff-line.removed { + background-color: #ffeef0; +} + +.diff-line.unchanged { + background-color: #fff; +} + +.diff-marker { + width: 20px; + padding: 0 5px; + text-align: center; + user-select: none; + color: #999; +} + +.diff-line.added .diff-marker { + color: #22863a; + background-color: #cdffd8; +} + +.diff-line.removed .diff-marker { + color: #cb2431; + background-color: #ffdce0; +} + +.diff-line pre { + margin: 0; + padding: 0 10px; + white-space: pre-wrap; + word-wrap: break-word; + flex: 1; +} diff --git a/genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.tsx b/genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.tsx new file mode 100644 index 0000000..2254acf --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/DiffViewerPanel.tsx @@ -0,0 +1,91 @@ +import { useMemo } from 'react'; +import { diffLines } from 'diff'; +import { useGenesis } from '../../context/GenesisContext'; +import './DiffViewerPanel.css'; + +export default function DiffViewerPanel() { + const { state } = useGenesis(); + const { selectedProgram, programs } = state; + + const diffData = useMemo(() => { + if (!selectedProgram || !selectedProgram.parent_id) return null; + + const parent = programs.find((p) => p.id === selectedProgram.parent_id); + if (!parent) return null; + + const changes = diffLines(parent.code, selectedProgram.code); + return { + parent, + changes, + }; + }, [selectedProgram, programs]); + + if (!selectedProgram) { + return ( +
+

Select a node to view its code diff.

+
+ ); + } + + if (!diffData) { + return ( +
+

+ {selectedProgram.parent_id + ? 'Parent program not found.' + : 'This is a root node with no parent to compare.'} +

+
+ ); + } + + const { parent, changes } = diffData; + + // Count additions and deletions + const additions = changes.filter((c) => c.added).reduce((sum, c) => sum + (c.count || 0), 0); + const deletions = changes.filter((c) => c.removed).reduce((sum, c) => sum + (c.count || 0), 0); + + return ( +
+
+

Code Diff

+
+ +{additions} additions + -{deletions} deletions +
+
+ +
+ + From: {parent.metadata.patch_name} (Gen{' '} + {parent.generation}) + + + + To: {selectedProgram.metadata.patch_name} (Gen{' '} + {selectedProgram.generation}) + +
+ +
+ {changes.map((part, index) => { + const className = part.added + ? 'diff-line added' + : part.removed + ? 'diff-line removed' + : 'diff-line unchanged'; + + return ( +
+ + {part.added ? '+' : part.removed ? '-' : ' '} + +
{part.value}
+
+ ); + })} +
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/EvaluationPanel.css b/genesis/webui/frontend/src/components/right-panel/EvaluationPanel.css new file mode 100644 index 0000000..2e22848 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/EvaluationPanel.css @@ -0,0 +1,86 @@ +.evaluation-panel { + padding: 10px; +} + +.evaluation-panel.empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; +} + +.evaluation-panel h4 { + margin: 0 0 15px 0; + color: #2c3e50; +} + +.eval-section { + margin-bottom: 20px; +} + +.eval-section h5 { + margin: 0 0 10px 0; + font-size: 14px; + color: #495057; + border-bottom: 1px solid #dee2e6; + padding-bottom: 6px; +} + +.score-display { + display: flex; + align-items: center; + gap: 20px; +} + +.score-display .score { + font-size: 24px; + font-weight: bold; +} + +.score-display .score.correct { + color: #2ecc71; +} + +.score-display .score.incorrect { + color: #e74c3c; +} + +.score-display .correctness { + font-size: 14px; + padding: 4px 10px; + border-radius: 3px; +} + +.metrics-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.metrics-table td { + padding: 8px; + border: 1px solid #e9ecef; +} + +.metrics-table .metric-key { + background: #f8f9fa; + font-weight: 500; + width: 40%; +} + +.metrics-table .metric-value { + font-family: monospace; +} + +.feedback-content { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 12px; + font-size: 13px; + white-space: pre-wrap; + word-wrap: break-word; + max-height: 300px; + overflow-y: auto; +} diff --git a/genesis/webui/frontend/src/components/right-panel/EvaluationPanel.tsx b/genesis/webui/frontend/src/components/right-panel/EvaluationPanel.tsx new file mode 100644 index 0000000..8b86606 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/EvaluationPanel.tsx @@ -0,0 +1,82 @@ +import { useGenesis } from '../../context/GenesisContext'; +import './EvaluationPanel.css'; + +export default function EvaluationPanel() { + const { state } = useGenesis(); + const { selectedProgram } = state; + + if (!selectedProgram) { + return ( +
+

Select a node to view its evaluation logs.

+
+ ); + } + + return ( +
+

Evaluation Results

+ +
+
Score
+
+ + {selectedProgram.combined_score?.toFixed(6) ?? 'N/A'} + + + {selectedProgram.correct ? '✓ Correct' : '✗ Incorrect'} + +
+
+ + {selectedProgram.public_metrics && ( +
+
Public Metrics
+ + + {Object.entries(selectedProgram.public_metrics).map( + ([key, value]) => ( + + + + + ) + )} + +
{key} + {typeof value === 'number' ? value.toFixed(6) : String(value)} +
+
+ )} + + {selectedProgram.private_metrics && ( +
+
Private Metrics
+ + + {Object.entries(selectedProgram.private_metrics).map( + ([key, value]) => ( + + + + + ) + )} + +
{key} + {typeof value === 'number' ? value.toFixed(6) : String(value)} +
+
+ )} + + {selectedProgram.text_feedback && ( +
+
Text Feedback
+
{selectedProgram.text_feedback}
+
+ )} +
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/LLMResultPanel.css b/genesis/webui/frontend/src/components/right-panel/LLMResultPanel.css new file mode 100644 index 0000000..e1406f7 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/LLMResultPanel.css @@ -0,0 +1,90 @@ +.llm-result-panel { + padding: 10px; +} + +.llm-result-panel.empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; +} + +.llm-result-panel h4 { + margin: 0 0 15px 0; + color: #2c3e50; +} + +.llm-result-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; + margin-bottom: 20px; +} + +.llm-result-table th, +.llm-result-table td { + border: 1px solid #ddd; + padding: 10px; + text-align: left; +} + +.llm-result-table th { + background-color: #f8f9fa; + font-weight: 600; + width: 150px; +} + +.llm-section { + margin-bottom: 15px; +} + +.llm-details { + border: 1px solid #e9ecef; + border-radius: 4px; +} + +.llm-details > summary { + cursor: pointer; + list-style: none; + padding: 10px 15px; + font-weight: 500; + color: #0366d6; + background: #f6f8fa; + border-radius: 4px 4px 0 0; +} + +.llm-details > summary::-webkit-details-marker { + display: none; +} + +.llm-summary::before { + content: '▶'; + display: inline-block; + font-size: 0.8em; + margin-right: 8px; + transition: transform 0.2s; +} + +.llm-details[open] > .llm-summary::before { + transform: rotate(90deg); +} + +.llm-details[open] > summary { + border-radius: 4px 4px 0 0; +} + +.llm-content { + padding: 15px; + background: #fff; +} + +.llm-content pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; + font-size: 13px; + line-height: 1.5; + max-height: 400px; + overflow-y: auto; +} diff --git a/genesis/webui/frontend/src/components/right-panel/LLMResultPanel.tsx b/genesis/webui/frontend/src/components/right-panel/LLMResultPanel.tsx new file mode 100644 index 0000000..ba4904f --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/LLMResultPanel.tsx @@ -0,0 +1,87 @@ +import { useGenesis } from '../../context/GenesisContext'; +import './LLMResultPanel.css'; + +export default function LLMResultPanel() { + const { state } = useGenesis(); + const { selectedProgram } = state; + + if (!selectedProgram) { + return ( +
+

Select a node to view the LLM result.

+
+ ); + } + + const llmResult = selectedProgram.llm_result; + + if (!llmResult) { + return ( +
+

No LLM result data available for this program.

+
+ ); + } + + return ( +
+

LLM Result

+ + + + {llmResult.model && ( + + + + + )} + {llmResult.prompt_tokens !== undefined && ( + + + + + )} + {llmResult.completion_tokens !== undefined && ( + + + + + )} + +
Model{llmResult.model}
Prompt Tokens{llmResult.prompt_tokens}
Completion Tokens{llmResult.completion_tokens}
+ + {llmResult.thought && ( +
+
+ Thought Process +
+
{llmResult.thought}
+
+
+
+ )} + + {llmResult.solution && ( +
+
+ Solution +
+
{llmResult.solution}
+
+
+
+ )} + + {llmResult.raw_response && ( +
+
+ Raw Response +
+
{llmResult.raw_response}
+
+
+
+ )} +
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.css b/genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.css new file mode 100644 index 0000000..f911022 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.css @@ -0,0 +1,80 @@ +.meta-info-panel { + padding: 10px; +} + +.meta-info-panel.empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; +} + +.meta-info-panel h3 { + margin: 0 0 20px 0; + color: #2c3e50; +} + +.stats-cards { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + flex: 1; + min-width: 200px; + background-color: #f8f9fa; + padding: 15px; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.stat-card h4 { + margin: 0 0 12px 0; + color: #2c3e50; + font-size: 15px; + border-bottom: 1px solid #dee2e6; + padding-bottom: 8px; +} + +.stat-card p { + margin: 6px 0; + font-size: 14px; + color: #495057; +} + +.stat-card p strong { + color: #343a40; +} + +.metric-good { + color: #2ecc71; + font-weight: bold; +} + +.charts-section { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.chart-container { + flex: 1; + min-width: 300px; +} + +.chart-container h4 { + margin: 0 0 10px 0; + font-size: 14px; + color: #495057; +} + +.chart-wrapper { + height: 200px; + background: #fff; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 10px; +} diff --git a/genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.tsx b/genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.tsx new file mode 100644 index 0000000..ec6712c --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/MetaInfoPanel.tsx @@ -0,0 +1,203 @@ +import { useMemo } from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + Filler, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +import { useGenesis } from '../../context/GenesisContext'; +import './MetaInfoPanel.css'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + Filler +); + +export default function MetaInfoPanel() { + const { state, stats } = useGenesis(); + const { programs } = state; + + const chartData = useMemo(() => { + if (programs.length === 0) return null; + + const byGeneration = new Map(); + programs.forEach((p) => { + if (!byGeneration.has(p.generation)) { + byGeneration.set(p.generation, []); + } + byGeneration.get(p.generation)!.push(p); + }); + + const generations = [...byGeneration.keys()].sort((a, b) => a - b); + const maxScores: number[] = []; + const cumulativeCosts: number[] = []; + let runningCost = 0; + let runningMax = 0; + + generations.forEach((gen) => { + const genPrograms = byGeneration.get(gen)!; + const correctPrograms = genPrograms.filter( + (p) => p.correct && p.combined_score !== null + ); + + if (correctPrograms.length > 0) { + const scores = correctPrograms.map((p) => p.combined_score as number); + runningMax = Math.max(runningMax, ...scores); + } + maxScores.push(runningMax); + + genPrograms.forEach((p) => { + runningCost += + (p.metadata.api_cost || 0) + + (p.metadata.embed_cost || 0) + + (p.metadata.novelty_cost || 0) + + (p.metadata.meta_cost || 0); + }); + cumulativeCosts.push(runningCost); + }); + + return { generations, maxScores, cumulativeCosts }; + }, [programs]); + + if (programs.length === 0) { + return ( +
+

Select a database to view evolution statistics.

+
+ ); + } + + const { bestProgram, totalCost, costBreakdown } = stats; + + return ( +
+

Meta Information

+ +
+
+

Overview

+

+ Total Generations: {stats.totalGenerations} +

+

+ Correct: {stats.correctPrograms} /{' '} + {stats.totalPrograms} +

+

+ Total Cost: ${totalCost.toFixed(4)} +

+

+ Avg Cost/Program: $ + {stats.avgCostPerProgram.toFixed(4)} +

+
+ +
+

Best Solution

+

+ Best Score:{' '} + {stats.bestScore.toFixed(4)} +

+

+ Name:{' '} + {bestProgram?.metadata.patch_name || 'N/A'} +

+

+ Generation: {bestProgram?.generation ?? 'N/A'} +

+

+ Island: {bestProgram?.island_idx ?? 'N/A'} +

+
+ +
+

Cost Breakdown

+

+ API Cost: ${costBreakdown.api.toFixed(4)} +

+

+ Embedding Cost: ${costBreakdown.embed.toFixed(4)} +

+

+ Novelty Cost: ${costBreakdown.novelty.toFixed(4)} +

+

+ Meta Cost: ${costBreakdown.meta.toFixed(4)} +

+
+
+ + {chartData && ( +
+
+

Performance Over Generations

+
+ `Gen ${g}`), + datasets: [ + { + label: 'Best Score (Cumulative)', + data: chartData.maxScores, + borderColor: '#2ecc71', + backgroundColor: 'rgba(46, 204, 113, 0.1)', + fill: true, + tension: 0.1, + }, + ], + }} + options={{ + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { position: 'top' }, + }, + }} + /> +
+
+ +
+

Cumulative Cost

+
+ `Gen ${g}`), + datasets: [ + { + label: 'Total Cost ($)', + data: chartData.cumulativeCosts, + borderColor: '#e74c3c', + backgroundColor: 'rgba(231, 76, 60, 0.1)', + fill: true, + tension: 0.1, + }, + ], + }} + options={{ + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { position: 'top' }, + }, + }} + /> +
+
+
+ )} +
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.css b/genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.css new file mode 100644 index 0000000..d5b6654 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.css @@ -0,0 +1,82 @@ +.node-details-panel { + padding: 10px; +} + +.node-details-panel.empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; +} + +.node-details-panel h4 { + margin: 0 0 15px 0; + padding-bottom: 10px; + border-bottom: 1px solid #dee2e6; + color: #2c3e50; +} + +.details-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 15px; + margin-bottom: 20px; +} + +.details-section { + padding: 12px; + background-color: #f8f9fa; + border-radius: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.details-section h5 { + margin: 0 0 10px 0; + font-size: 14px; + color: #495057; + border-bottom: 1px solid #dee2e6; + padding-bottom: 6px; +} + +.details-section p { + margin: 5px 0; + font-size: 13px; + word-wrap: break-word; +} + +.details-section p strong { + color: #343a40; + min-width: 100px; + display: inline-block; +} + +.mono { + font-family: monospace; + font-size: 11px; + word-break: break-all; +} + +.metrics-section, +.feedback-section { + margin-top: 15px; +} + +.metrics-section h5, +.feedback-section h5 { + margin: 0 0 10px 0; + font-size: 14px; + color: #495057; +} + +.metrics-section pre, +.feedback-section pre { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 12px; + font-size: 12px; + overflow-x: auto; + white-space: pre-wrap; + word-wrap: break-word; +} diff --git a/genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.tsx b/genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.tsx new file mode 100644 index 0000000..a3642bb --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/NodeDetailsPanel.tsx @@ -0,0 +1,102 @@ +import { useGenesis } from '../../context/GenesisContext'; +import './NodeDetailsPanel.css'; + +export default function NodeDetailsPanel() { + const { state } = useGenesis(); + const { selectedProgram } = state; + + if (!selectedProgram) { + return ( +
+

Select a node to view its details.

+
+ ); + } + + const formatValue = (value: unknown): string => { + if (value === null || value === undefined) return 'N/A'; + if (typeof value === 'number') return value.toFixed(6); + if (typeof value === 'boolean') return value ? 'Yes' : 'No'; + if (typeof value === 'object') return JSON.stringify(value, null, 2); + return String(value); + }; + + return ( +
+

Node Details: {selectedProgram.metadata.patch_name}

+ +
+
+
Basic Info
+

+ ID:{' '} + {selectedProgram.id} +

+

+ Parent ID:{' '} + {selectedProgram.parent_id || 'None'} +

+

+ Generation: {selectedProgram.generation} +

+

+ Timestamp:{' '} + {new Date(selectedProgram.timestamp * 1000).toLocaleString()} +

+

+ Language: {selectedProgram.language} +

+
+ +
+
Performance
+

+ Combined Score:{' '} + {formatValue(selectedProgram.combined_score)} +

+

+ Correct: {selectedProgram.correct ? '✓ Yes' : '✗ No'} +

+

+ Complexity: {formatValue(selectedProgram.complexity)} +

+

+ Island: {selectedProgram.island_idx ?? 'N/A'} +

+
+ +
+
Metadata
+

+ Patch Type: {selectedProgram.metadata.patch_type} +

+

+ Model: {selectedProgram.metadata.model || 'N/A'} +

+

+ API Cost: $ + {(selectedProgram.metadata.api_cost || 0).toFixed(4)} +

+

+ Embed Cost: $ + {(selectedProgram.metadata.embed_cost || 0).toFixed(4)} +

+
+
+ + {selectedProgram.public_metrics && ( +
+
Public Metrics
+
{JSON.stringify(selectedProgram.public_metrics, null, 2)}
+
+ )} + + {selectedProgram.text_feedback && ( +
+
Text Feedback
+
{selectedProgram.text_feedback}
+
+ )} +
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.css b/genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.css new file mode 100644 index 0000000..d2e4d31 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.css @@ -0,0 +1,134 @@ +.pareto-front-panel { + display: flex; + flex-direction: column; + height: 100%; + padding: 10px; + box-sizing: border-box; +} + +.pareto-front-panel.empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; +} + +.pareto-controls { + display: flex; + flex-wrap: wrap; + gap: 15px; + margin-bottom: 10px; + padding: 10px; + background: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.pareto-control-group { + display: flex; + align-items: center; + gap: 8px; +} + +.pareto-control-group label { + font-size: 13px; + font-weight: 600; + color: #495057; + min-width: 50px; +} + +.pareto-control-group select { + padding: 6px 10px; + font-size: 13px; + border: 1px solid #ced4da; + border-radius: 4px; + background: white; + color: #495057; + min-width: 140px; + cursor: pointer; +} + +.pareto-control-group select:hover { + border-color: #adb5bd; +} + +.pareto-control-group select:focus { + outline: none; + border-color: #80bdff; + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); +} + +.objective-toggle { + display: flex; + border: 1px solid #ced4da; + border-radius: 4px; + overflow: hidden; +} + +.objective-toggle button { + padding: 6px 12px; + font-size: 12px; + font-weight: 500; + border: none; + background: white; + color: #6c757d; + cursor: pointer; + transition: all 0.15s ease; +} + +.objective-toggle button:first-child { + border-right: 1px solid #ced4da; +} + +.objective-toggle button:hover { + background: #f8f9fa; +} + +.objective-toggle button.active { + background: #e58e26; + color: white; +} + +.pareto-stats { + display: flex; + justify-content: flex-end; + padding: 5px 10px; + font-size: 12px; + color: #6c757d; +} + +.pareto-stats span { + background: #f1f3f4; + padding: 4px 10px; + border-radius: 4px; +} + +.pareto-chart-container { + flex: 1; + min-height: 300px; + position: relative; +} + +/* Ensure Plotly chart fills the container */ +.pareto-chart-container .js-plotly-plot, +.pareto-chart-container .plotly { + width: 100% !important; + height: 100% !important; +} + +/* Responsive adjustments */ +@media (max-width: 600px) { + .pareto-controls { + flex-direction: column; + gap: 10px; + } + + .pareto-control-group { + flex-wrap: wrap; + } + + .pareto-control-group select { + min-width: 100px; + } +} diff --git a/genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.tsx b/genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.tsx new file mode 100644 index 0000000..772a74f --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/ParetoFrontPanel.tsx @@ -0,0 +1,441 @@ +import { useState, useMemo, useCallback, useEffect } from 'react'; +import Plot from 'react-plotly.js'; +import type { PlotMouseEvent, Data, Layout } from 'plotly.js'; +import { useGenesis } from '../../context/GenesisContext'; +import { + getAvailableParetoMetrics, + calculateParetoFront, + getNestedValue, + formatMetricValue, + type ParetoMetric, + type ParetoPoint, +} from '../../utils/pareto'; +import './ParetoFrontPanel.css'; + +export default function ParetoFrontPanel() { + const { state, selectProgram } = useGenesis(); + const { programs } = state; + + // Get available metrics + const availableMetrics = useMemo( + () => getAvailableParetoMetrics(programs), + [programs] + ); + + // State for axis selections + const [yAxisPath, setYAxisPath] = useState('combined_score'); + const [xAxisPath, setXAxisPath] = useState('complexity'); + const [yObjective, setYObjective] = useState<'min' | 'max'>('max'); + const [xObjective, setXObjective] = useState<'min' | 'max'>('min'); + + // Update defaults when metrics are discovered + useEffect(() => { + if (availableMetrics.length > 0) { + // Set Y-axis default + const yDefault = availableMetrics.find( + (m) => m.path === 'combined_score' + ); + if (yDefault) { + setYAxisPath(yDefault.path); + setYObjective(yDefault.objective); + } + + // Set X-axis default: prefer api_cost, then complexity + const xDefault = + availableMetrics.find((m) => m.path === 'metadata.api_cost') || + availableMetrics.find((m) => m.path === 'complexity'); + if (xDefault) { + setXAxisPath(xDefault.path); + setXObjective(xDefault.objective); + } + } + }, [availableMetrics]); + + // Get current metric objects + const yMetric: ParetoMetric | undefined = useMemo(() => { + const base = availableMetrics.find((m) => m.path === yAxisPath); + return base ? { ...base, objective: yObjective } : undefined; + }, [availableMetrics, yAxisPath, yObjective]); + + const xMetric: ParetoMetric | undefined = useMemo(() => { + const base = availableMetrics.find((m) => m.path === xAxisPath); + return base ? { ...base, objective: xObjective } : undefined; + }, [availableMetrics, xAxisPath, xObjective]); + + // Calculate Pareto data + const paretoData = useMemo(() => { + if (!xMetric || !yMetric || programs.length === 0) return null; + return calculateParetoFront(programs, xMetric, yMetric); + }, [programs, xMetric, yMetric]); + + // Handle axis change + const handleYAxisChange = useCallback( + (path: string) => { + setYAxisPath(path); + const metric = availableMetrics.find((m) => m.path === path); + if (metric) setYObjective(metric.objective); + }, + [availableMetrics] + ); + + const handleXAxisChange = useCallback( + (path: string) => { + setXAxisPath(path); + const metric = availableMetrics.find((m) => m.path === path); + if (metric) setXObjective(metric.objective); + }, + [availableMetrics] + ); + + // Handle point click + const handleClick = useCallback( + (event: PlotMouseEvent) => { + if (event.points.length > 0) { + const point = event.points[0]; + const programId = point.customdata as string; + const program = programs.find((p) => p.id === programId); + if (program) { + selectProgram(program); + } + } + }, + [programs, selectProgram] + ); + + // Build hover text for a point + const buildHoverText = useCallback( + (point: ParetoPoint): string => { + const p = point.program; + let text = `${p.metadata?.patch_name || p.id}
`; + text += `Combined Score: ${formatMetricValue(p.combined_score)}
`; + + if (xMetric) { + const xValue = getNestedValue(p, xMetric.path) as number; + text += `${xMetric.name}: ${formatMetricValue(xValue)}
`; + } + if (yMetric) { + const yValue = getNestedValue(p, yMetric.path) as number; + text += `${yMetric.name}: ${formatMetricValue(yValue)}
`; + } + + return text; + }, + [xMetric, yMetric] + ); + + // Empty state + if (programs.length === 0) { + return ( +
+

Load a database to view Pareto front.

+
+ ); + } + + if (availableMetrics.length < 2) { + return ( +
+

Not enough numeric metrics available for Pareto analysis.

+
+ ); + } + + if (!paretoData || !xMetric || !yMetric) { + return ( +
+

Select metrics to view Pareto front.

+
+ ); + } + + const { allPoints, paretoPoints } = paretoData; + + // Categorize points + const incorrectPoints = allPoints.filter((p) => !p.program.correct); + const correctDominatedPoints = allPoints.filter( + (p) => p.program.correct && !paretoPoints.includes(p) + ); + + // Sort Pareto points by x for the boundary line + const sortedPareto = [...paretoPoints].sort((a, b) => a.x - b.x); + + // Calculate bounds for correct programs + const correctPointsForBounds = [...paretoPoints, ...correctDominatedPoints]; + const hasCorrectPoints = correctPointsForBounds.length > 0; + + const correctX = correctPointsForBounds.map((p) => p.x); + const correctY = correctPointsForBounds.map((p) => p.y); + const xRange = hasCorrectPoints + ? Math.max(...correctX) - Math.min(...correctX) + : 1; + const yRange = hasCorrectPoints + ? Math.max(...correctY) - Math.min(...correctY) + : 1; + + // Build Plotly traces + const traces: Data[] = []; + + // Dominated region fill (render first so it's behind everything) + if (sortedPareto.length > 0 && hasCorrectPoints) { + const xFill: number[] = []; + const yFill: number[] = []; + + const paretoX = sortedPareto.map((p) => p.x); + const paretoY = sortedPareto.map((p) => p.y); + + const extendRight = xMetric.objective === 'min'; + const extendUp = yMetric.objective === 'min'; + + const minX = Math.min(...correctX); + const maxX = Math.max(...correctX); + const minY = Math.min(...correctY); + const maxY = Math.max(...correctY); + const xPad = xRange * 0.05; + const yPad = yRange * 0.05; + + if (extendRight && !extendUp) { + // Dominated region is bottom-right + xFill.push(minX); + yFill.push(paretoY[0]); + for (let i = 0; i < paretoX.length; i++) { + xFill.push(paretoX[i]); + yFill.push(paretoY[i]); + } + xFill.push(maxX); + yFill.push(paretoY[paretoY.length - 1]); + xFill.push(maxX); + yFill.push(minY); + xFill.push(minX); + yFill.push(minY); + } else if (!extendRight && !extendUp) { + // Dominated region is bottom-left + xFill.push(maxX + xPad); + yFill.push(paretoY[paretoY.length - 1]); + for (let i = paretoX.length - 1; i >= 0; i--) { + xFill.push(paretoX[i]); + yFill.push(paretoY[i]); + } + xFill.push(minX - xPad); + yFill.push(paretoY[0]); + xFill.push(minX - xPad); + yFill.push(minY - yPad); + xFill.push(maxX + xPad); + yFill.push(minY - yPad); + } else if (extendRight && extendUp) { + // Dominated region is top-right + xFill.push(minX - xPad); + yFill.push(paretoY[0]); + for (let i = 0; i < paretoX.length; i++) { + xFill.push(paretoX[i]); + yFill.push(paretoY[i]); + } + xFill.push(maxX + xPad); + yFill.push(paretoY[paretoY.length - 1]); + xFill.push(maxX + xPad); + yFill.push(maxY + yPad); + xFill.push(minX - xPad); + yFill.push(maxY + yPad); + } else { + // Dominated region is top-left + xFill.push(maxX + xPad); + yFill.push(paretoY[paretoY.length - 1]); + for (let i = paretoX.length - 1; i >= 0; i--) { + xFill.push(paretoX[i]); + yFill.push(paretoY[i]); + } + xFill.push(minX - xPad); + yFill.push(paretoY[0]); + xFill.push(minX - xPad); + yFill.push(maxY + yPad); + xFill.push(maxX + xPad); + yFill.push(maxY + yPad); + } + + traces.push({ + x: xFill, + y: yFill, + fill: 'toself', + fillcolor: 'rgba(231, 76, 60, 0.15)', + line: { width: 0 }, + mode: 'none', + type: 'scatter', + hoverinfo: 'none', + showlegend: false, + name: 'Dominated Region', + }); + } + + // Dominated correct points (gray) + traces.push({ + x: correctDominatedPoints.map((p) => p.x), + y: correctDominatedPoints.map((p) => p.y), + mode: 'markers', + type: 'scatter', + name: 'Dominated', + text: correctDominatedPoints.map(buildHoverText), + hoverinfo: 'text', + marker: { color: 'rgba(150, 150, 150, 0.5)', size: 8 }, + customdata: correctDominatedPoints.map((p) => p.program.id), + }); + + // Incorrect points (red X) + traces.push({ + x: incorrectPoints.map((p) => p.x), + y: incorrectPoints.map((p) => p.y), + mode: 'markers', + type: 'scatter', + name: 'Incorrect', + text: incorrectPoints.map(buildHoverText), + hoverinfo: 'text', + marker: { color: '#e74c3c', size: 8, symbol: 'x' }, + customdata: incorrectPoints.map((p) => p.program.id), + }); + + // Pareto-optimal points (orange diamonds) + traces.push({ + x: paretoPoints.map((p) => p.x), + y: paretoPoints.map((p) => p.y), + mode: 'markers', + type: 'scatter', + name: 'Pareto Optimal', + text: paretoPoints.map(buildHoverText), + hoverinfo: 'text', + marker: { color: '#e58e26', size: 12, symbol: 'diamond' }, + customdata: paretoPoints.map((p) => p.program.id), + }); + + // Pareto boundary line + if (sortedPareto.length > 1) { + traces.push({ + x: sortedPareto.map((p) => p.x), + y: sortedPareto.map((p) => p.y), + mode: 'lines', + type: 'scatter', + name: 'Pareto Boundary', + line: { + color: '#e58e26', + width: 2, + shape: 'linear', + }, + hoverinfo: 'none', + showlegend: false, + }); + } + + // Layout + const xPadding = xRange * 0.1; + const yPadding = yRange * 0.1; + + const layout: Partial = { + title: { text: 'Pareto Front Analysis' }, + xaxis: { + title: { text: `${xMetric.name} (${xMetric.objective === 'min' ? 'Lower' : 'Higher'} is better)` }, + autorange: xMetric.objective === 'min' ? 'reversed' : true, + ...(xMetric.objective !== 'min' && + hasCorrectPoints && { + range: [ + Math.min(...correctX) - xPadding, + Math.max(...correctX) + xPadding, + ], + }), + }, + yaxis: { + title: { text: `${yMetric.name} (${yMetric.objective === 'min' ? 'Lower' : 'Higher'} is better)` }, + autorange: yMetric.objective === 'min' ? 'reversed' : true, + ...(yMetric.objective !== 'min' && + hasCorrectPoints && { + range: [ + Math.min(...correctY) - yPadding, + Math.max(...correctY) + yPadding, + ], + }), + }, + hovermode: 'closest', + showlegend: true, + legend: { + x: 1, + xanchor: 'right', + y: 1, + }, + margin: { t: 50, r: 10, b: 60, l: 70 }, + }; + + return ( +
+
+
+ + +
+ + +
+
+ +
+ + +
+ + +
+
+
+ +
+ + Total: {allPoints.length} | Correct: {correctDominatedPoints.length + paretoPoints.length} | Pareto Optimal: {paretoPoints.length} + +
+ +
+ +
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/RightPanel.css b/genesis/webui/frontend/src/components/right-panel/RightPanel.css new file mode 100644 index 0000000..feb9190 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/RightPanel.css @@ -0,0 +1,90 @@ +.right-panel-content { + display: flex; + flex-direction: column; + height: 100%; + padding: 15px 20px; +} + +.node-summary { + padding: 10px; + background-color: #f1f8ff; + border-left: 4px solid #2196f3; + border-radius: 3px; + font-size: 14px; + margin-bottom: 15px; + display: flex; + align-items: center; + gap: 15px; + flex-wrap: wrap; +} + +.node-summary strong { + color: #2c3e50; +} + +.summary-item { + color: #666; +} + +.summary-item.correct { + color: #2ecc71; + font-weight: bold; +} + +.summary-item.incorrect { + color: #e74c3c; + font-weight: bold; +} + +.right-tabs { + display: flex; + border-bottom: 1px solid #ddd; + flex-shrink: 0; + gap: 2px; + flex-wrap: wrap; +} + +.right-tab { + padding: 10px 15px; + cursor: pointer; + border: 1px solid transparent; + border-bottom: none; + background-color: #f1f1f1; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + font-size: 14px; + color: #333; + transition: background-color 0.2s; +} + +.right-tab:hover { + background-color: #e9ecef; +} + +.right-tab.active { + background-color: #fff; + border-color: #ddd; + border-bottom-color: #fff; + margin-bottom: -1px; + padding-bottom: 11px; + font-weight: bold; + color: #2c3e50; +} + +.right-tab-content { + flex: 1; + overflow: auto; + background-color: #fff; + padding: 15px; + border: 1px solid #ddd; + border-top: none; +} + +.empty-state { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #666; + font-size: 14px; +} diff --git a/genesis/webui/frontend/src/components/right-panel/RightPanel.tsx b/genesis/webui/frontend/src/components/right-panel/RightPanel.tsx new file mode 100644 index 0000000..56a2029 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/RightPanel.tsx @@ -0,0 +1,100 @@ +import { useGenesis } from '../../context/GenesisContext'; +import MetaInfoPanel from './MetaInfoPanel'; +import ParetoFrontPanel from './ParetoFrontPanel'; +import ScratchpadPanel from './ScratchpadPanel'; +import NodeDetailsPanel from './NodeDetailsPanel'; +import CodeViewerPanel from './CodeViewerPanel'; +import DiffViewerPanel from './DiffViewerPanel'; +import EvaluationPanel from './EvaluationPanel'; +import LLMResultPanel from './LLMResultPanel'; +import './RightPanel.css'; + +const RIGHT_TABS = [ + { id: 'meta-info', label: 'Meta' }, + { id: 'pareto-front', label: 'Pareto Front' }, + { id: 'scratchpad', label: 'Scratchpad' }, + { id: 'node-details', label: 'Node' }, + { id: 'code-viewer', label: 'Code' }, + { id: 'diff-viewer', label: 'Diff' }, + { id: 'evaluation', label: 'Evaluation' }, + { id: 'llm-result', label: 'LLM Result' }, +]; + +export default function RightPanel() { + const { state, setRightTab } = useGenesis(); + const { selectedRightTab, selectedProgram, programs } = state; + + const renderContent = () => { + switch (selectedRightTab) { + case 'meta-info': + return ; + case 'pareto-front': + return ; + case 'scratchpad': + return ; + case 'node-details': + return ; + case 'code-viewer': + return ; + case 'diff-viewer': + return ; + case 'evaluation': + return ; + case 'llm-result': + return ; + default: + return ; + } + }; + + // Show node summary if a program is selected + const renderNodeSummary = () => { + if (!selectedProgram) return null; + + const score = + selectedProgram.combined_score !== null + ? selectedProgram.combined_score.toFixed(4) + : 'N/A'; + + return ( +
+ {selectedProgram.metadata.patch_name} + Gen: {selectedProgram.generation} + Score: {score} + + {selectedProgram.correct ? '✓ Correct' : '✗ Incorrect'} + +
+ ); + }; + + return ( +
+ {renderNodeSummary()} + +
+ {RIGHT_TABS.map((tab) => ( + + ))} +
+ +
+ {programs.length === 0 ? ( +
+

Select a database to view evolution results.

+
+ ) : ( + renderContent() + )} +
+
+ ); +} diff --git a/genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.css b/genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.css new file mode 100644 index 0000000..9712282 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.css @@ -0,0 +1,149 @@ +.scratchpad-panel { + height: 100%; + display: flex; + flex-direction: column; +} + +.scratchpad-panel.empty { + align-items: center; + justify-content: center; + color: #666; +} + +.scratchpad-controls { + display: flex; + gap: 15px; + align-items: center; + flex-wrap: wrap; + padding: 10px; + background: #f8f9fa; + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; +} + +.generation-control { + display: flex; + align-items: center; + gap: 10px; +} + +.generation-control input[type='range'] { + width: 200px; +} + +.gen-value { + min-width: 30px; + font-weight: 600; +} + +.scratchpad-controls button { + padding: 6px 12px; + border: 1px solid #ddd; + border-radius: 4px; + background: #fff; + cursor: pointer; + font-size: 13px; +} + +.scratchpad-controls button:hover:not(:disabled) { + background: #e9ecef; +} + +.scratchpad-controls button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.sub-tabs { + display: flex; + gap: 5px; + padding: 10px; + border-bottom: 2px solid #e0e0e0; +} + +.sub-tab { + padding: 8px 16px; + cursor: pointer; + border: none; + background: none; + border-bottom: 2px solid transparent; + font-size: 14px; + font-weight: 500; + color: #666; + transition: all 0.2s; + margin-bottom: -2px; +} + +.sub-tab:hover { + background-color: #f0f0f0; +} + +.sub-tab.active { + border-bottom-color: #007bff; + color: #007bff; + background-color: #f8f9fa; +} + +.scratchpad-content { + flex: 1; + overflow: auto; + padding: 15px; + background: #fff; + border: 1px solid #ddd; + border-top: none; + border-radius: 0 0 4px 4px; +} + +.content-header { + padding-bottom: 10px; + margin-bottom: 15px; + border-bottom: 1px solid #eee; +} + +.content-header h5 { + margin: 0 0 5px 0; + color: #333; +} + +.content-header .filename { + font-size: 13px; + color: #666; +} + +.loading { + text-align: center; + color: #666; + padding: 20px; +} + +.meta-content-body { + line-height: 1.6; +} + +.meta-content-body h1, +.meta-content-body h2, +.meta-content-body h3, +.meta-content-body h4 { + color: #333; + margin-top: 20px; + margin-bottom: 10px; +} + +.meta-content-body p { + margin: 10px 0; +} + +.meta-content-body code { + background-color: #f4f4f4; + padding: 2px 4px; + border-radius: 3px; + font-family: monospace; +} + +.meta-content-body pre { + background-color: #f8f8f8; + border: 1px solid #ddd; + border-radius: 4px; + padding: 12px; + overflow-x: auto; +} diff --git a/genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.tsx b/genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.tsx new file mode 100644 index 0000000..dd3cde8 --- /dev/null +++ b/genesis/webui/frontend/src/components/right-panel/ScratchpadPanel.tsx @@ -0,0 +1,212 @@ +import { useState, useEffect, useCallback } from 'react'; +import { marked } from 'marked'; +import { useGenesis } from '../../context/GenesisContext'; +import { getMetaFiles, getMetaContent, getMetaPdfUrl } from '../../services/api'; +import type { MetaFile, MetaContent } from '../../types'; +import './ScratchpadPanel.css'; + +const SUB_TABS = [ + { id: 'individual-programs', label: 'Individual Programs' }, + { id: 'global-insights', label: 'Global Insights' }, + { id: 'meta-recommendations', label: 'Meta Recommendations' }, +]; + +interface ParsedSections { + individualPrograms: string; + globalInsights: string; + metaRecommendations: string; +} + +export default function ScratchpadPanel() { + const { state } = useGenesis(); + const { currentDbPath } = state; + + const [metaFiles, setMetaFiles] = useState([]); + const [selectedGeneration, setSelectedGeneration] = useState( + null + ); + const [content, setContent] = useState(null); + const [parsedSections, setParsedSections] = useState( + null + ); + const [activeSubTab, setActiveSubTab] = useState('individual-programs'); + const [isLoading, setIsLoading] = useState(false); + + // Load meta files when database changes + useEffect(() => { + if (!currentDbPath) { + setMetaFiles([]); + setContent(null); + return; + } + + setIsLoading(true); + getMetaFiles(currentDbPath) + .then((files) => { + setMetaFiles(files); + if (files.length > 0) { + // Select highest generation by default + const maxGen = Math.max(...files.map((f) => f.generation)); + setSelectedGeneration(maxGen); + } + }) + .catch(console.error) + .finally(() => setIsLoading(false)); + }, [currentDbPath]); + + // Load content when generation changes + useEffect(() => { + if (!currentDbPath || selectedGeneration === null) { + setContent(null); + return; + } + + setIsLoading(true); + getMetaContent(currentDbPath, selectedGeneration) + .then((data) => { + setContent(data); + // Parse content into sections + const sections = parseMetaIntoSections(data.content); + setParsedSections(sections); + }) + .catch(console.error) + .finally(() => setIsLoading(false)); + }, [currentDbPath, selectedGeneration]); + + const handleRefresh = useCallback(() => { + if (currentDbPath) { + setIsLoading(true); + getMetaFiles(currentDbPath) + .then(setMetaFiles) + .catch(console.error) + .finally(() => setIsLoading(false)); + } + }, [currentDbPath]); + + const handleDownloadPdf = useCallback(() => { + if (currentDbPath && selectedGeneration !== null) { + window.open(getMetaPdfUrl(currentDbPath, selectedGeneration), '_blank'); + } + }, [currentDbPath, selectedGeneration]); + + if (!currentDbPath) { + return ( +
+

Select a database to view scratchpad.

+
+ ); + } + + if (metaFiles.length === 0 && !isLoading) { + return ( +
+

No meta analysis files found for this database.

+
+ ); + } + + const minGen = metaFiles.length > 0 ? Math.min(...metaFiles.map((f) => f.generation)) : 0; + const maxGen = metaFiles.length > 0 ? Math.max(...metaFiles.map((f) => f.generation)) : 0; + + const renderSectionContent = () => { + if (!parsedSections) return null; + + let html = ''; + switch (activeSubTab) { + case 'individual-programs': + html = parsedSections.individualPrograms || '

No content available.

'; + break; + case 'global-insights': + html = parsedSections.globalInsights || '

No content available.

'; + break; + case 'meta-recommendations': + html = parsedSections.metaRecommendations || '

No content available.

'; + break; + } + + return
; + }; + + return ( +
+
+
+ + setSelectedGeneration(Number(e.target.value))} + disabled={metaFiles.length === 0} + /> + {selectedGeneration} +
+ + + +
+ +
+ {SUB_TABS.map((tab) => ( + + ))} +
+ +
+ {isLoading ? ( +

Loading...

+ ) : content ? ( + <> +
+
+ Scratchpad - Generation {content.generation} +
+ {content.filename} +
+ {renderSectionContent()} + + ) : ( +

No content available.

+ )} +
+
+ ); +} + +function parseMetaIntoSections(content: string): ParsedSections { + const sections: ParsedSections = { + individualPrograms: '', + globalInsights: '', + metaRecommendations: '', + }; + + const parts = content.split( + /^#\s*(INDIVIDUAL PROGRAM SUMMARIES|GLOBAL INSIGHTS SCRATCHPAD|META RECOMMENDATIONS)$/m + ); + + for (let i = 1; i < parts.length; i += 2) { + const sectionTitle = parts[i]?.trim(); + const sectionContent = parts[i + 1] || ''; + + if (sectionTitle === 'INDIVIDUAL PROGRAM SUMMARIES') { + sections.individualPrograms = marked.parse(sectionContent.trim()) as string; + } else if (sectionTitle === 'GLOBAL INSIGHTS SCRATCHPAD') { + sections.globalInsights = marked.parse(sectionContent.trim()) as string; + } else if (sectionTitle === 'META RECOMMENDATIONS') { + sections.metaRecommendations = marked.parse(sectionContent.trim()) as string; + } + } + + return sections; +} diff --git a/genesis/webui/frontend/src/context/GenesisContext.tsx b/genesis/webui/frontend/src/context/GenesisContext.tsx new file mode 100644 index 0000000..20f2f02 --- /dev/null +++ b/genesis/webui/frontend/src/context/GenesisContext.tsx @@ -0,0 +1,294 @@ +import { + createContext, + useContext, + useReducer, + useCallback, + useEffect, + useRef, + type ReactNode, +} from 'react'; +import type { + DatabaseInfo, + Program, + TasksAndResults, + AppState, + EvolutionStats, +} from '../types'; +import { listDatabases, getPrograms } from '../services/api'; + +// Action types +type Action = + | { type: 'SET_DATABASES'; payload: DatabaseInfo[] } + | { type: 'SET_TASKS_AND_RESULTS'; payload: TasksAndResults } + | { type: 'SET_CURRENT_DB'; payload: string | null } + | { type: 'SET_PROGRAMS'; payload: Program[] } + | { type: 'SET_SELECTED_PROGRAM'; payload: Program | null } + | { type: 'SET_LEFT_TAB'; payload: string } + | { type: 'SET_RIGHT_TAB'; payload: string } + | { type: 'SET_LOADING'; payload: boolean } + | { type: 'SET_ERROR'; payload: string | null } + | { type: 'SET_AUTO_REFRESH'; payload: boolean }; + +const initialState: AppState = { + databases: [], + tasksAndResults: {}, + currentDbPath: null, + programs: [], + selectedProgram: null, + selectedLeftTab: 'tree-view', + selectedRightTab: 'meta-info', + isLoading: false, + error: null, + autoRefreshEnabled: false, +}; + +function reducer(state: AppState, action: Action): AppState { + switch (action.type) { + case 'SET_DATABASES': + return { ...state, databases: action.payload }; + case 'SET_TASKS_AND_RESULTS': + return { ...state, tasksAndResults: action.payload }; + case 'SET_CURRENT_DB': + return { ...state, currentDbPath: action.payload }; + case 'SET_PROGRAMS': + return { ...state, programs: action.payload }; + case 'SET_SELECTED_PROGRAM': + return { ...state, selectedProgram: action.payload }; + case 'SET_LEFT_TAB': + return { ...state, selectedLeftTab: action.payload }; + case 'SET_RIGHT_TAB': + return { ...state, selectedRightTab: action.payload }; + case 'SET_LOADING': + return { ...state, isLoading: action.payload }; + case 'SET_ERROR': + return { ...state, error: action.payload }; + case 'SET_AUTO_REFRESH': + return { ...state, autoRefreshEnabled: action.payload }; + default: + return state; + } +} + +// Helper to organize databases by task +function organizeDatabases(dbs: DatabaseInfo[]): TasksAndResults { + const tasksAndResults: TasksAndResults = {}; + + dbs.forEach((db) => { + const pathParts = db.path.split('/'); + if (pathParts.length >= 3) { + const task = pathParts[pathParts.length - 3]; + const result = pathParts[pathParts.length - 2]; + + if (!tasksAndResults[task]) { + tasksAndResults[task] = []; + } + + tasksAndResults[task].push({ + name: result, + path: db.path, + sortKey: db.sort_key || '0', + }); + } + }); + + // Sort results within each task by date (newest first) + Object.keys(tasksAndResults).forEach((task) => { + tasksAndResults[task].sort((a, b) => b.sortKey.localeCompare(a.sortKey)); + }); + + return tasksAndResults; +} + +// Compute evolution stats +function computeStats(programs: Program[]): EvolutionStats { + const correctPrograms = programs.filter((p) => p.correct); + const generations = [...new Set(programs.map((p) => p.generation))]; + + let totalApiCost = 0; + let totalEmbedCost = 0; + let totalNoveltyCost = 0; + let totalMetaCost = 0; + + programs.forEach((p) => { + totalApiCost += p.metadata.api_cost || 0; + totalEmbedCost += p.metadata.embed_cost || 0; + totalNoveltyCost += p.metadata.novelty_cost || 0; + totalMetaCost += p.metadata.meta_cost || 0; + }); + + const totalCost = + totalApiCost + totalEmbedCost + totalNoveltyCost + totalMetaCost; + + // Find best program (correct only) + let bestProgram: Program | null = null; + let bestScore = -Infinity; + correctPrograms.forEach((p) => { + if (p.combined_score !== null && p.combined_score > bestScore) { + bestScore = p.combined_score; + bestProgram = p; + } + }); + + return { + totalGenerations: generations.length, + totalPrograms: programs.length, + correctPrograms: correctPrograms.length, + totalCost, + avgCostPerProgram: programs.length > 0 ? totalCost / programs.length : 0, + bestScore: bestScore === -Infinity ? 0 : bestScore, + bestProgram, + costBreakdown: { + api: totalApiCost, + embed: totalEmbedCost, + novelty: totalNoveltyCost, + meta: totalMetaCost, + }, + }; +} + +// Context value type +interface GenesisContextValue { + state: AppState; + stats: EvolutionStats; + loadDatabases: (force?: boolean) => Promise; + loadDatabase: (dbPath: string) => Promise; + selectProgram: (program: Program | null) => void; + setLeftTab: (tab: string) => void; + setRightTab: (tab: string) => void; + setAutoRefresh: (enabled: boolean) => void; + refreshData: () => Promise; +} + +const GenesisContext = createContext(null); + +export function GenesisProvider({ children }: { children: ReactNode }) { + const [state, dispatch] = useReducer(reducer, initialState); + const autoRefreshRef = useRef | null>(null); + + const stats = computeStats(state.programs); + + const loadDatabases = useCallback( + async (force = false) => { + if (state.databases.length > 0 && !force) return; + + dispatch({ type: 'SET_LOADING', payload: true }); + dispatch({ type: 'SET_ERROR', payload: null }); + + try { + const dbs = await listDatabases(); + dispatch({ type: 'SET_DATABASES', payload: dbs }); + dispatch({ + type: 'SET_TASKS_AND_RESULTS', + payload: organizeDatabases(dbs), + }); + } catch (error) { + dispatch({ + type: 'SET_ERROR', + payload: error instanceof Error ? error.message : 'Unknown error', + }); + } finally { + dispatch({ type: 'SET_LOADING', payload: false }); + } + }, + [state.databases.length] + ); + + const loadDatabase = useCallback(async (dbPath: string) => { + dispatch({ type: 'SET_LOADING', payload: true }); + dispatch({ type: 'SET_ERROR', payload: null }); + dispatch({ type: 'SET_CURRENT_DB', payload: dbPath }); + + try { + const programs = await getPrograms(dbPath); + // Sort by generation and timestamp + programs.sort((a, b) => { + if (a.generation !== b.generation) + return a.generation - b.generation; + return a.timestamp - b.timestamp; + }); + // Add iter_id + const genCounters: Record = {}; + programs.forEach((p) => { + if (!genCounters[p.generation]) genCounters[p.generation] = 0; + p.iter_id = genCounters[p.generation]++; + }); + dispatch({ type: 'SET_PROGRAMS', payload: programs }); + } catch (error) { + dispatch({ + type: 'SET_ERROR', + payload: error instanceof Error ? error.message : 'Unknown error', + }); + } finally { + dispatch({ type: 'SET_LOADING', payload: false }); + } + }, []); + + const selectProgram = useCallback((program: Program | null) => { + dispatch({ type: 'SET_SELECTED_PROGRAM', payload: program }); + }, []); + + const setLeftTab = useCallback((tab: string) => { + dispatch({ type: 'SET_LEFT_TAB', payload: tab }); + }, []); + + const setRightTab = useCallback((tab: string) => { + dispatch({ type: 'SET_RIGHT_TAB', payload: tab }); + }, []); + + const refreshData = useCallback(async () => { + if (state.currentDbPath) { + await loadDatabase(state.currentDbPath); + } + }, [state.currentDbPath, loadDatabase]); + + const setAutoRefresh = useCallback( + (enabled: boolean) => { + dispatch({ type: 'SET_AUTO_REFRESH', payload: enabled }); + if (enabled && state.currentDbPath) { + if (autoRefreshRef.current) { + clearInterval(autoRefreshRef.current); + } + autoRefreshRef.current = setInterval(refreshData, 3000); + } else if (autoRefreshRef.current) { + clearInterval(autoRefreshRef.current); + autoRefreshRef.current = null; + } + }, + [state.currentDbPath, refreshData] + ); + + // Cleanup auto-refresh on unmount + useEffect(() => { + return () => { + if (autoRefreshRef.current) { + clearInterval(autoRefreshRef.current); + } + }; + }, []); + + return ( + + {children} + + ); +} + +export function useGenesis() { + const context = useContext(GenesisContext); + if (!context) { + throw new Error('useGenesis must be used within a GenesisProvider'); + } + return context; +} diff --git a/genesis/webui/frontend/src/index.css b/genesis/webui/frontend/src/index.css index cbc84c0..d888f7a 100644 --- a/genesis/webui/frontend/src/index.css +++ b/genesis/webui/frontend/src/index.css @@ -4,6 +4,12 @@ padding: 0; } +html, +body { + height: 100%; + overflow: hidden; +} + :root { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, @@ -21,9 +27,7 @@ } body { - margin: 0; min-width: 320px; - min-height: 100vh; } a { @@ -35,24 +39,21 @@ a:hover { text-decoration: underline; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #3498db; - color: white; - cursor: pointer; - transition: background-color 0.2s; +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; } -button:hover { - background-color: #2980b9; +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; } -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; } diff --git a/genesis/webui/frontend/src/services/api.ts b/genesis/webui/frontend/src/services/api.ts new file mode 100644 index 0000000..f7f5427 --- /dev/null +++ b/genesis/webui/frontend/src/services/api.ts @@ -0,0 +1,56 @@ +import type { DatabaseInfo, Program, MetaFile, MetaContent } from '../types'; + +const API_BASE = ''; + +export async function listDatabases(): Promise { + const response = await fetch(`${API_BASE}/list_databases`); + if (!response.ok) { + throw new Error(`Failed to load database list (HTTP ${response.status})`); + } + return response.json(); +} + +export async function getPrograms(dbPath: string): Promise { + const response = await fetch( + `${API_BASE}/get_programs?db_path=${encodeURIComponent(dbPath)}` + ); + if (!response.ok) { + if (response.status === 503) { + throw new Error( + 'Database temporarily unavailable - evolution may be running' + ); + } + throw new Error(`Failed to load data (HTTP ${response.status})`); + } + return response.json(); +} + +export async function getMetaFiles(dbPath: string): Promise { + const response = await fetch( + `${API_BASE}/get_meta_files?db_path=${encodeURIComponent(dbPath)}` + ); + if (!response.ok) { + if (response.status === 404) { + return []; + } + throw new Error(`Failed to load meta files (HTTP ${response.status})`); + } + return response.json(); +} + +export async function getMetaContent( + dbPath: string, + generation: number +): Promise { + const response = await fetch( + `${API_BASE}/get_meta_content?db_path=${encodeURIComponent(dbPath)}&generation=${generation}` + ); + if (!response.ok) { + throw new Error(`Failed to load meta content (HTTP ${response.status})`); + } + return response.json(); +} + +export function getMetaPdfUrl(dbPath: string, generation: number): string { + return `${API_BASE}/download_meta_pdf?db_path=${encodeURIComponent(dbPath)}&generation=${generation}`; +} diff --git a/genesis/webui/frontend/src/types/index.ts b/genesis/webui/frontend/src/types/index.ts new file mode 100644 index 0000000..cfd205d --- /dev/null +++ b/genesis/webui/frontend/src/types/index.ts @@ -0,0 +1,117 @@ +// Program data from database +export interface Program { + id: string; + parent_id: string | null; + code: string; + language: string; + generation: number; + timestamp: number; + agent_name: string; + combined_score: number | null; + public_metrics: Record | null; + private_metrics: Record | null; + text_feedback: string | null; + metadata: ProgramMetadata; + complexity: number | null; + embedding: number[] | null; + embedding_pca_2d: [number, number] | null; + embedding_pca_3d: [number, number, number] | null; + island_idx: number | null; + correct: boolean; + llm_result?: LLMResult | null; + iter_id?: number; +} + +export interface ProgramMetadata { + patch_name: string; + patch_type: 'init' | 'full' | 'diff' | 'cross' | string; + model?: string; + api_cost?: number; + embed_cost?: number; + novelty_cost?: number; + meta_cost?: number; + [key: string]: unknown; +} + +export interface LLMResult { + thought?: string; + solution?: string; + raw_response?: string; + prompt_tokens?: number; + completion_tokens?: number; + model?: string; + [key: string]: unknown; +} + +// Database listing +export interface DatabaseInfo { + path: string; + name: string; + actual_path?: string; + sort_key?: string; +} + +// Meta files +export interface MetaFile { + generation: number; + filename: string; + path: string; +} + +export interface MetaContent { + generation: number; + filename: string; + content: string; +} + +// Organized databases by task +export interface TasksAndResults { + [taskName: string]: { + name: string; + path: string; + sortKey: string; + }[]; +} + +// Tree node for D3 visualization +export interface TreeNode extends Program { + isUnifiedRoot?: boolean; + isVirtual?: boolean; +} + +// Sort state +export interface SortState { + key: string; + direction: 'asc' | 'desc'; +} + +// Application state +export interface AppState { + databases: DatabaseInfo[]; + tasksAndResults: TasksAndResults; + currentDbPath: string | null; + programs: Program[]; + selectedProgram: Program | null; + selectedLeftTab: string; + selectedRightTab: string; + isLoading: boolean; + error: string | null; + autoRefreshEnabled: boolean; +} + +// Computed stats +export interface EvolutionStats { + totalGenerations: number; + totalPrograms: number; + correctPrograms: number; + totalCost: number; + avgCostPerProgram: number; + bestScore: number; + bestProgram: Program | null; + costBreakdown: { + api: number; + embed: number; + novelty: number; + meta: number; + }; +} diff --git a/genesis/webui/frontend/src/types/react-plotly.d.ts b/genesis/webui/frontend/src/types/react-plotly.d.ts new file mode 100644 index 0000000..4773ddf --- /dev/null +++ b/genesis/webui/frontend/src/types/react-plotly.d.ts @@ -0,0 +1,42 @@ +declare module 'react-plotly.js' { + import { Component } from 'react'; + import Plotly from 'plotly.js'; + + export interface PlotParams { + data: Plotly.Data[]; + layout?: Partial; + config?: Partial; + frames?: Plotly.Frame[]; + style?: React.CSSProperties; + className?: string; + useResizeHandler?: boolean; + divId?: string; + revision?: number; + onInitialized?: ( + figure: Readonly<{ data: Plotly.Data[]; layout: Partial }>, + graphDiv: HTMLElement + ) => void; + onUpdate?: ( + figure: Readonly<{ data: Plotly.Data[]; layout: Partial }>, + graphDiv: HTMLElement + ) => void; + onPurge?: ( + figure: Readonly<{ data: Plotly.Data[]; layout: Partial }>, + graphDiv: HTMLElement + ) => void; + onError?: (err: Error) => void; + onClick?: (event: Plotly.PlotMouseEvent) => void; + onHover?: (event: Plotly.PlotHoverEvent) => void; + onUnhover?: (event: Plotly.PlotMouseEvent) => void; + onSelected?: (event: Plotly.PlotSelectionEvent) => void; + onDeselect?: () => void; + onRelayout?: (event: Plotly.PlotRelayoutEvent) => void; + onRestyle?: (event: Plotly.PlotRestyleEvent) => void; + onRedraw?: () => void; + onClickAnnotation?: (event: Plotly.ClickAnnotationEvent) => void; + onLegendClick?: (event: Plotly.LegendClickEvent) => boolean; + onLegendDoubleClick?: (event: Plotly.LegendClickEvent) => boolean; + } + + export default class Plot extends Component {} +} diff --git a/genesis/webui/frontend/src/utils/pareto.ts b/genesis/webui/frontend/src/utils/pareto.ts new file mode 100644 index 0000000..acdfc45 --- /dev/null +++ b/genesis/webui/frontend/src/utils/pareto.ts @@ -0,0 +1,173 @@ +import type { Program } from '../types'; + +export interface ParetoMetric { + name: string; + path: string; + objective: 'min' | 'max'; +} + +export interface ParetoPoint { + x: number; + y: number; + program: Program; +} + +export interface ParetoData { + allPoints: ParetoPoint[]; + paretoPoints: ParetoPoint[]; +} + +/** + * Get a nested value from an object using dot notation + * e.g., getNestedValue(obj, 'public_metrics.accuracy') -> obj.public_metrics.accuracy + */ +export function getNestedValue(obj: unknown, path: string): unknown { + if (!path) return undefined; + const parts = path.split('.'); + let current: unknown = obj; + for (const part of parts) { + if (current === null || typeof current === 'undefined') return undefined; + current = (current as Record)[part]; + } + return current; +} + +/** + * Discover all numeric metrics available across programs + */ +export function getAvailableParetoMetrics(programs: Program[]): ParetoMetric[] { + if (!programs || programs.length === 0) return []; + + const metricsMap = new Map(); + + const addMetric = (program: Program, path: string) => { + const value = getNestedValue(program, path); + if (typeof value === 'number' && !metricsMap.has(path)) { + // Create a human-readable name + let name = path + .split('.') + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join(' '); + name = name.replace(/_/g, ' '); + + if (path.startsWith('public_metrics.')) { + name = `Public: ${path.split('.')[1]}`; + } + if (path.startsWith('private_metrics.')) { + name = `Private: ${path.split('.')[1]}`; + } + + // Infer default objective + let objective: 'min' | 'max' = 'max'; + const key = path.split('.').pop() || ''; + if ( + key.includes('cost') || + key.includes('time') || + key.includes('complexity') || + key.includes('latency') + ) { + objective = 'min'; + } + + metricsMap.set(path, { name, path, objective }); + } + }; + + const discoverInObject = ( + program: Program, + subObj: Record | null, + prefix: string + ) => { + if (!subObj) return; + for (const key in subObj) { + if (typeof subObj[key] === 'number') { + addMetric(program, `${prefix}.${key}`); + } + } + }; + + programs.forEach((p) => { + addMetric(p, 'combined_score'); + addMetric(p, 'complexity'); + if (p.metadata) { + addMetric(p, 'metadata.api_cost'); + addMetric(p, 'metadata.embed_cost'); + addMetric(p, 'metadata.novelty_cost'); + addMetric(p, 'metadata.meta_cost'); + } + discoverInObject( + p, + p.public_metrics as Record | null, + 'public_metrics' + ); + discoverInObject( + p, + p.private_metrics as Record | null, + 'private_metrics' + ); + }); + + return Array.from(metricsMap.values()); +} + +/** + * Calculate which points lie on the Pareto frontier + */ +export function calculateParetoFront( + programs: Program[], + xMetric: ParetoMetric, + yMetric: ParetoMetric +): ParetoData { + // Extract all points with valid numeric values for both metrics + const allPoints: ParetoPoint[] = programs + .map((p) => { + const x = getNestedValue(p, xMetric.path); + const y = getNestedValue(p, yMetric.path); + if (typeof x === 'number' && typeof y === 'number') { + return { x, y, program: p }; + } + return null; + }) + .filter((p): p is ParetoPoint => p !== null); + + // Only consider correct programs for Pareto optimality + const correctPoints = allPoints.filter((p) => p.program.correct); + + // Find Pareto-optimal points among correct programs + const paretoPoints = correctPoints.filter((p1) => { + return !correctPoints.some((p2) => { + if (p1 === p2) return false; + + // Check if p2 dominates p1 + const xP2Better = + xMetric.objective === 'min' ? p2.x < p1.x : p2.x > p1.x; + const yP2Better = + yMetric.objective === 'min' ? p2.y < p1.y : p2.y > p1.y; + const xEqual = p2.x === p1.x; + const yEqual = p2.y === p1.y; + + // p2 dominates p1 if p2 is better or equal in both objectives and strictly better in at least one + if ( + (xP2Better && (yP2Better || yEqual)) || + (yP2Better && (xP2Better || xEqual)) + ) { + return true; + } + return false; + }); + }); + + return { allPoints, paretoPoints }; +} + +/** + * Format a numeric value for display + */ +export function formatMetricValue(value: number | null | undefined): string { + if (value === null || value === undefined) return 'N/A'; + if (Number.isInteger(value)) return value.toString(); + if (Math.abs(value) < 0.001 || Math.abs(value) >= 10000) { + return value.toExponential(3); + } + return value.toFixed(4); +} diff --git a/genesis/webui/frontend/vite.config.ts b/genesis/webui/frontend/vite.config.ts index 8b0f57b..3d3b146 100644 --- a/genesis/webui/frontend/vite.config.ts +++ b/genesis/webui/frontend/vite.config.ts @@ -1,7 +1,16 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], -}) + server: { + proxy: { + '/list_databases': 'http://localhost:8000', + '/get_programs': 'http://localhost:8000', + '/get_meta_files': 'http://localhost:8000', + '/get_meta_content': 'http://localhost:8000', + '/download_meta_pdf': 'http://localhost:8000', + }, + }, +}); diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..b57c255 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,73 @@ +site_name: Genesis +site_description: LLM-Driven Program Evolution Platform +site_url: https://genesis.ai +repo_url: https://github.com/GeorgePearse/Genesis +repo_name: GeorgePearse/Genesis + +theme: + name: material + palette: + - scheme: default + primary: teal + accent: purple + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: teal + accent: purple + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - navigation.top + - navigation.indexes + - content.code.copy + - content.tabs.link + +plugins: + - search + - mkdocstrings: + default_handler: python + handlers: + python: + paths: [.] + options: + show_root_heading: true + show_source: true + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - attr_list + - md_in_html + - tables + - toc: + permalink: true + +nav: + - Home: index.md + - Getting Started: getting_started.md + - Guides: + - Configuration: configuration.md + - WebUI: webui.md + - Local LLM Support: support_local_llm.md + - Developer Guide: developer_guide.md + - Roadmap: roadmap.md + - Recent Papers: papers.md + - API Reference: + - Core: + - Runner: api/core/runner.md + - Evolution Config: api/core/config.md + - Database: api/database.md + - Launch: api/launch.md