From 64a10f018fb773940319b294f33481c4a62b7f10 Mon Sep 17 00:00:00 2001
From: Rajkoli145 <2024.rajk@isu.ac.in>
Date: Sun, 24 May 2026 12:52:38 +0530
Subject: [PATCH 01/10] Add live testing backend UI and student registry
---
backend/data/students.json | 62 +
backend/src/agents/base.py | 2 +-
backend/src/agents/gatekeeper/agent.py | 9 +-
.../gatekeeper/registry/registry_store.py | 55 +
backend/src/agents/oracle/agent.py | 39 +-
backend/src/agents/sentinel/agent.py | 3 +-
backend/src/main.py | 3 +-
.../intelligence/flows/api_flow_analyzer.py | 1 +
.../intelligence/flows/auth_flow_analyzer.py | 1 +
.../intelligence/flows/db_flow_analyzer.py | 15 +-
scripts/seed_students.py | 24 +
testing_backend_ui/README.md | 31 +
testing_backend_ui/index.html | 18 +
testing_backend_ui/package-lock.json | 2208 +++++++++++++++++
testing_backend_ui/package.json | 20 +
testing_backend_ui/src/App.jsx | 994 ++++++++
testing_backend_ui/src/main.jsx | 10 +
testing_backend_ui/src/styles.css | 531 ++++
testing_backend_ui/vite.config.js | 6 +
19 files changed, 4006 insertions(+), 26 deletions(-)
create mode 100644 backend/data/students.json
create mode 100644 scripts/seed_students.py
create mode 100644 testing_backend_ui/README.md
create mode 100644 testing_backend_ui/index.html
create mode 100644 testing_backend_ui/package-lock.json
create mode 100644 testing_backend_ui/package.json
create mode 100644 testing_backend_ui/src/App.jsx
create mode 100644 testing_backend_ui/src/main.jsx
create mode 100644 testing_backend_ui/src/styles.css
create mode 100644 testing_backend_ui/vite.config.js
diff --git a/backend/data/students.json b/backend/data/students.json
new file mode 100644
index 0000000..3d33e8d
--- /dev/null
+++ b/backend/data/students.json
@@ -0,0 +1,62 @@
+[
+ {
+ "schema_version": "1.0.0",
+ "roll_number": "CS2021001",
+ "full_name": "Aman Koli",
+ "email": "aman.koli@college.edu",
+ "department": "CS",
+ "year": "3",
+ "batch": "A",
+ "program": "B.Tech",
+ "photo_reference": "photos/CS2021001.jpg",
+ "is_active": true
+ },
+ {
+ "schema_version": "1.0.0",
+ "roll_number": "CS2021002",
+ "full_name": "Raj Koli",
+ "email": "raj.koli@college.edu",
+ "department": "CS",
+ "year": "3",
+ "batch": "A",
+ "program": "B.Tech",
+ "photo_reference": "photos/CS2021002.jpg",
+ "is_active": true
+ },
+ {
+ "schema_version": "1.0.0",
+ "roll_number": "IT2022010",
+ "full_name": "Priya Sharma",
+ "email": "priya.sharma@college.edu",
+ "department": "IT",
+ "year": "2",
+ "batch": "B",
+ "program": "B.Tech",
+ "photo_reference": "photos/IT2022010.jpg",
+ "is_active": true
+ },
+ {
+ "schema_version": "1.0.0",
+ "roll_number": "DS2020005",
+ "full_name": "Neha Patel",
+ "email": "neha.patel@college.edu",
+ "department": "DS",
+ "year": "4",
+ "batch": "C",
+ "program": "B.Tech",
+ "photo_reference": "photos/DS2020005.jpg",
+ "is_active": true
+ },
+ {
+ "schema_version": "1.0.0",
+ "roll_number": "AI2023001",
+ "full_name": "Arjun Mehta",
+ "email": "arjun.mehta@college.edu",
+ "department": "AI",
+ "year": "1",
+ "batch": "A",
+ "program": "B.Tech",
+ "photo_reference": null,
+ "is_active": true
+ }
+]
diff --git a/backend/src/agents/base.py b/backend/src/agents/base.py
index f9899a7..6f5a234 100644
--- a/backend/src/agents/base.py
+++ b/backend/src/agents/base.py
@@ -14,7 +14,7 @@ def __init__(self, name: str):
def emit_event(self, session_id: str, event_type: EventType, payload: Dict[str, Any]):
"""Standard method for agents to emit structured events."""
# Also dispatch a repository event for progress updates
- if event_type == "agent.progress" and self.github_token:
+ if str(event_type) == EventType.AGENT_PROGRESS.value and self.github_token:
self._dispatch_github_event(session_id, payload)
return EventEmitter.emit(
diff --git a/backend/src/agents/gatekeeper/agent.py b/backend/src/agents/gatekeeper/agent.py
index 9ad984a..c830738 100644
--- a/backend/src/agents/gatekeeper/agent.py
+++ b/backend/src/agents/gatekeeper/agent.py
@@ -8,6 +8,7 @@
from typing import Any, Dict, Optional
from src.agents.base import BaseAgent
+from src.models.events import EventType
from .registry.registry_store import StudentRegistry, SAMPLE_STUDENTS
from .registry.lookup import RegistryLookup, LookupResult, LookupFailureReason
@@ -20,7 +21,9 @@ def __init__(self, registry: Optional[StudentRegistry] = None):
self._registry = registry
else:
self._registry = StudentRegistry()
- self._registry.seed(SAMPLE_STUDENTS)
+ # Only seed the sample fixture if the persistent registry is empty
+ if len(self._registry) == 0:
+ self._registry.seed(SAMPLE_STUDENTS)
self._lookup = RegistryLookup(self._registry)
async def process(
@@ -61,7 +64,7 @@ async def send_log(msg: str, log_type: str = "info"):
f"[Gatekeeper] ✅ Student verified: {result.profile.full_name}", "success"
)
self.emit_event(
- session_id, "AGENT_PROGRESS",
+ session_id, EventType.AGENT_PROGRESS,
{
"agent": "Gatekeeper",
"status": "complete",
@@ -83,7 +86,7 @@ async def send_log(msg: str, log_type: str = "info"):
f"[Gatekeeper] ❌ Rejected: {result.message}", "error"
)
self.emit_event(
- session_id, "AGENT_PROGRESS",
+ session_id, EventType.AGENT_PROGRESS,
{
"agent": "Gatekeeper",
"status": "failed",
diff --git a/backend/src/agents/gatekeeper/registry/registry_store.py b/backend/src/agents/gatekeeper/registry/registry_store.py
index 083b61e..3da1afd 100644
--- a/backend/src/agents/gatekeeper/registry/registry_store.py
+++ b/backend/src/agents/gatekeeper/registry/registry_store.py
@@ -22,7 +22,9 @@
from __future__ import annotations
+import json
import logging
+import os
from typing import Dict, Iterator, List, Optional
from .student_schema import (
@@ -56,6 +58,15 @@ class StudentRegistry:
def __init__(self) -> None:
self._store: Dict[str, StudentProfile] = {}
+ # Attempt to load persistent registry from backend/data/students.json if present
+ try:
+ base = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+ default_path = os.path.join(base, 'backend', 'data', 'students.json')
+ if os.path.exists(default_path):
+ self.load_from_file(default_path)
+ except Exception:
+ # ignore persistence errors for in-memory fallback
+ pass
# ── Write operations ──────────────────────────────────────────────────────
@@ -90,6 +101,50 @@ def seed(self, profiles: List[StudentProfile]) -> "StudentRegistry":
for p in profiles:
self.upsert(p)
logger.info("[StudentRegistry] seeded %d student records.", len(profiles))
+ try:
+ # attempt to persist after seeding
+ base = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+ default_path = os.path.join(base, 'backend', 'data', 'students.json')
+ self.save_to_file(default_path)
+ except Exception:
+ pass
+ return self
+
+ # ── Persistence helpers ─────────────────────────────────────────────────
+ def to_serializable(self) -> List[dict]:
+ return [p.to_dict() for p in self._store.values()]
+
+ def save_to_file(self, path: str) -> None:
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(self.to_serializable(), f, indent=2)
+
+ def load_from_file(self, path: str) -> "StudentRegistry":
+ try:
+ with open(path, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+ # Expect list of dicts
+ for item in data:
+ try:
+ # Convert enum strings back to model types via StudentProfile
+ profile = StudentProfile(
+ roll_number=item.get('roll_number'),
+ full_name=item.get('full_name'),
+ email=item.get('email'),
+ department=item.get('department'),
+ year=item.get('year'),
+ batch=item.get('batch'),
+ program=item.get('program', 'B.Tech'),
+ photo_reference=item.get('photo_reference'),
+ guardian_name=item.get('guardian_name'),
+ is_active=item.get('is_active', True),
+ )
+ self.upsert(profile)
+ except Exception:
+ continue
+ logger.info("[StudentRegistry] loaded %d records from %s", len(self._store), path)
+ except Exception as e:
+ logger.warning("[StudentRegistry] failed to load from %s: %s", path, e)
return self
# ── Read operations ───────────────────────────────────────────────────────
diff --git a/backend/src/agents/oracle/agent.py b/backend/src/agents/oracle/agent.py
index 7656601..d1f61c6 100644
--- a/backend/src/agents/oracle/agent.py
+++ b/backend/src/agents/oracle/agent.py
@@ -105,9 +105,16 @@ async def send_log(msg: str, type: str = "info"):
await send_log("[Oracle] Extracting observable engineering signals...", "info")
if repo_path:
observable_signals = ObservableSignalsEngine.extract_signals(repo_path, repo_structure, repo_detections, project_graph)
- self.emit_event(session_id, "OBSERVABLE_SIGNALS_EXTRACTED", {
+ self.emit_event(session_id, EventType.AGENT_PROGRESS, {
+ "agent": "Oracle",
+ "status": "running",
+ "milestone": "Observable signals extracted",
"signal_count": len(observable_signals),
- "critical_signals": len([s for s in observable_signals if s.risk_level == "high"]),
+ "critical_signals": len([
+ s for s in observable_signals
+ if getattr(s, "risk_level", None) == "high"
+ or (isinstance(s, dict) and s.get("risk_level") == "high")
+ ]),
})
# ===== Architecture Inference (AST-based, deterministic) =====
@@ -128,9 +135,16 @@ async def send_log(msg: str, type: str = "info"):
failure_scenarios = ExecutionGraphFailureAnalyzer.analyze_failure_scenarios(
repo_path, repo_structure, repo_detections, observable_signals, project_graph
)
- self.emit_event(session_id, "FAILURE_SCENARIOS_ANALYZED", {
+ self.emit_event(session_id, EventType.AGENT_PROGRESS, {
+ "agent": "Oracle",
+ "status": "running",
+ "milestone": "Failure scenarios analyzed",
"scenario_count": len(failure_scenarios),
- "critical_scenarios": len([s for s in failure_scenarios if s.propagation_risk == "critical"]),
+ "critical_scenarios": len([
+ s for s in failure_scenarios
+ if getattr(s, "propagation_risk", None) == "critical"
+ or (isinstance(s, dict) and s.get("propagation_risk") == "critical")
+ ]),
})
# ===== PHASE 2: Evidence-Grounded Viva Generation =====
@@ -140,11 +154,14 @@ async def send_log(msg: str, type: str = "info"):
grounded_viva_targets = EvidenceGroundedVivaGenerator.generate_questions(
failure_scenarios, observable_signals, repo_detections, repo_path
)
- self.emit_event(session_id, "EVIDENCE_GROUNDED_VIVA_GENERATED", {
+ self.emit_event(session_id, EventType.AGENT_PROGRESS, {
+ "agent": "Oracle",
+ "status": "running",
+ "milestone": "Evidence-grounded viva generated",
"viva_count": len(grounded_viva_targets),
- "difficulty_breakdown": f"hard: {len([v for v in grounded_viva_targets if v.difficulty == 'hard'])}, "
- f"medium: {len([v for v in grounded_viva_targets if v.difficulty == 'medium'])}, "
- f"foundational: {len([v for v in grounded_viva_targets if v.difficulty == 'foundational'])}",
+ "difficulty_breakdown": f"hard: {len([v for v in grounded_viva_targets if getattr(v, 'difficulty', None) == 'hard'])}, "
+ f"medium: {len([v for v in grounded_viva_targets if getattr(v, 'difficulty', None) == 'medium'])}, "
+ f"foundational: {len([v for v in grounded_viva_targets if getattr(v, 'difficulty', None) == 'foundational'])}",
})
# Fallback viva generation (backward compatibility)
@@ -173,10 +190,6 @@ async def send_log(msg: str, type: str = "info"):
complexity_mismatch=complexity_mismatch
)
- # Attach Phase 2 evidence-grounded analysis to context
- context.observable_signals = observable_signals
- context.failure_scenarios = failure_scenarios
-
# 5. Implementation Intelligence Phase
if repo_url and repo_path:
self.log_info("Starting implementation flow analysis...")
@@ -184,7 +197,7 @@ async def send_log(msg: str, type: str = "info"):
context = ImplementationFlowEngine.analyze_implementation(repo_path, repo_structure, context)
self.emit_event(session_id, EventType.IMPLEMENTATION_FLOW_DETECTED, {"nodes": len(context.execution_graph.nodes)})
- self.emit_event(session_id, "AGENT_PROGRESS", {"agent": "Oracle", "status": "complete", "milestone": "Submission Intelligence"})
+ self.emit_event(session_id, EventType.AGENT_PROGRESS, {"agent": "Oracle", "status": "complete", "milestone": "Submission Intelligence"})
await send_log("[Oracle] Submission intelligence complete.", "success")
return context
diff --git a/backend/src/agents/sentinel/agent.py b/backend/src/agents/sentinel/agent.py
index 42c1b08..f6ee6a3 100644
--- a/backend/src/agents/sentinel/agent.py
+++ b/backend/src/agents/sentinel/agent.py
@@ -1,5 +1,6 @@
from typing import Any, Dict
from src.agents.base import BaseAgent
+from src.models.events import EventType
from src.models.context import StructuredContext
class SentinelAgent(BaseAgent):
@@ -20,6 +21,6 @@ async def send_log(msg: str, type: str = "info"):
# In the future, this agent will analyze user interaction, detect anomalies, etc.
# For now, it's a pass-through.
- self.emit_event(session_id, "AGENT_PROGRESS", {"agent": "Sentinel", "status": "complete", "milestone": "Behavior Analyzed (Placeholder)"})
+ self.emit_event(session_id, EventType.AGENT_PROGRESS, {"agent": "Sentinel", "status": "complete", "milestone": "Behavior Analyzed (Placeholder)"})
return input_data
diff --git a/backend/src/main.py b/backend/src/main.py
index b09d416..1ac1863 100644
--- a/backend/src/main.py
+++ b/backend/src/main.py
@@ -20,6 +20,7 @@
class AnalyzeRequest(BaseModel):
repo_url: str
report_path: Optional[str] = None
+ roll_number: Optional[str] = None
enable_viva: bool = True
enable_debug: bool = True
generate_report: bool = False
@@ -66,7 +67,7 @@ async def resolve_alert(request: ResolveAlertRequest):
@app.post("/analyze")
async def analyze_repo(request: AnalyzeRequest):
# Legacy REST endpoint for backward compatibility
- input_data = {"repo_url": request.repo_url, "report_path": request.report_path}
+ input_data = {"repo_url": request.repo_url, "report_path": request.report_path, "roll_number": request.roll_number}
context = await main_agent.process("api_session", input_data)
try:
data = context.model_dump()
diff --git a/backend/src/services/intelligence/flows/api_flow_analyzer.py b/backend/src/services/intelligence/flows/api_flow_analyzer.py
index e9dd08c..aa54e01 100644
--- a/backend/src/services/intelligence/flows/api_flow_analyzer.py
+++ b/backend/src/services/intelligence/flows/api_flow_analyzer.py
@@ -1,3 +1,4 @@
+import os
from typing import List, Dict, Any
from ..intermediate_representation.execution_graph_builder import ExecutionGraphBuilder
from ....models.context import ImplementationFlow, FlowNodeType
diff --git a/backend/src/services/intelligence/flows/auth_flow_analyzer.py b/backend/src/services/intelligence/flows/auth_flow_analyzer.py
index acba62a..21e50df 100644
--- a/backend/src/services/intelligence/flows/auth_flow_analyzer.py
+++ b/backend/src/services/intelligence/flows/auth_flow_analyzer.py
@@ -1,3 +1,4 @@
+import os
from typing import List, Dict, Any
from ..intermediate_representation.execution_graph_builder import ExecutionGraphBuilder
from ....models.context import ImplementationFlow, FlowNodeType
diff --git a/backend/src/services/intelligence/flows/db_flow_analyzer.py b/backend/src/services/intelligence/flows/db_flow_analyzer.py
index 881b8f7..ae3120c 100644
--- a/backend/src/services/intelligence/flows/db_flow_analyzer.py
+++ b/backend/src/services/intelligence/flows/db_flow_analyzer.py
@@ -1,3 +1,4 @@
+import os
from typing import List, Dict, Any
from ..intermediate_representation.execution_graph_builder import ExecutionGraphBuilder
from ....models.context import ImplementationFlow, FlowNodeType
@@ -32,13 +33,13 @@ def analyze(repo_path: str, structure: Dict[str, Any], builder: ExecutionGraphBu
if not any(marker in content for marker in db_markers):
continue
- builder.add_node(
- node_id=f"db_{file_path}",
- label=f"Data Access Layer ({file_path})",
- node_type=FlowNodeType.DB_QUERY
- )
- steps.append(f"DB interaction point: {file_path}")
- evidence.append(f"Persistence related keywords found in {file_path}")
+ builder.add_node(
+ node_id=f"db_{file_path}",
+ label=f"Data Access Layer ({file_path})",
+ node_type=FlowNodeType.DB_QUERY
+ )
+ steps.append(f"DB interaction point: {file_path}")
+ evidence.append(f"Persistence related keywords found in {file_path}")
return ImplementationFlow(
steps=steps,
diff --git a/scripts/seed_students.py b/scripts/seed_students.py
new file mode 100644
index 0000000..8571cd5
--- /dev/null
+++ b/scripts/seed_students.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+"""Seed the student registry persistence file with sample records.
+
+Usage:
+ python scripts/seed_students.py
+
+This writes `backend/data/students.json` with the sample students defined
+in the Gatekeeper registry fixtures.
+"""
+import json
+import os
+from backend.src.agents.gatekeeper.registry.registry_store import SAMPLE_STUDENTS
+
+def main():
+ base = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+ out_path = os.path.join(base, 'backend', 'data', 'students.json')
+ os.makedirs(os.path.dirname(out_path), exist_ok=True)
+ serializable = [s.to_dict() for s in SAMPLE_STUDENTS]
+ with open(out_path, 'w', encoding='utf-8') as f:
+ json.dump(serializable, f, indent=2)
+ print(f"Seeded {len(serializable)} students to {out_path}")
+
+if __name__ == '__main__':
+ main()
diff --git a/testing_backend_ui/README.md b/testing_backend_ui/README.md
new file mode 100644
index 0000000..9df19cb
--- /dev/null
+++ b/testing_backend_ui/README.md
@@ -0,0 +1,31 @@
+# ORACLE Backend Testing UI (Step 1)
+
+Initial React dashboard scaffolding with:
+
+- Agent Topology Graph (React Flow)
+- Session Timeline (Gantt style)
+- Live Event Feed (schema-shaped events)
+- Alert Cards (SENTINEL + GATEKEEPER)
+
+## Run
+
+```bash
+npm install
+npm run dev
+```
+
+## Build check
+
+```bash
+npm run build
+```
+
+## Notes
+
+- Agent topology uses required fixed layout:
+ - ORACLE (top)
+ - MAIN VIVA (middle)
+ - GATEKEEPER + SENTINEL (bottom, side-by-side)
+- Event objects follow the required schema fields:
+ - event_id, timestamp, source_agent, target_agent, event_type, session_id, payload, duration_ms
+- This is step 1 foundation; next step can connect these panels to live backend websocket streams.
diff --git a/testing_backend_ui/index.html b/testing_backend_ui/index.html
new file mode 100644
index 0000000..1a03966
--- /dev/null
+++ b/testing_backend_ui/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+ ORACLE Backend Testing UI
+
+
+
+
+
+
+
+
+
diff --git a/testing_backend_ui/package-lock.json b/testing_backend_ui/package-lock.json
new file mode 100644
index 0000000..c2e5b5b
--- /dev/null
+++ b/testing_backend_ui/package-lock.json
@@ -0,0 +1,2208 @@
+{
+ "name": "testing-backend-ui",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "testing-backend-ui",
+ "version": "0.1.0",
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "reactflow": "^11.11.4"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.1",
+ "vite": "^5.4.10"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
+ "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@reactflow/background": {
+ "version": "11.3.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz",
+ "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/controls": {
+ "version": "11.2.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
+ "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==",
+ "license": "MIT",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/core": {
+ "version": "11.11.4",
+ "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
+ "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3": "^7.4.0",
+ "@types/d3-drag": "^3.0.1",
+ "@types/d3-selection": "^3.0.3",
+ "@types/d3-zoom": "^3.0.1",
+ "classcat": "^5.0.3",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/minimap": {
+ "version": "11.7.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
+ "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "@types/d3-selection": "^3.0.3",
+ "@types/d3-zoom": "^3.0.1",
+ "classcat": "^5.0.3",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/node-resizer": {
+ "version": "2.2.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
+ "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==",
+ "license": "MIT",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.4",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/node-toolbar": {
+ "version": "1.3.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
+ "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
+ "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
+ "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
+ "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
+ "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
+ "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
+ "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
+ "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
+ "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
+ "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
+ "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
+ "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
+ "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
+ "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
+ "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
+ "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
+ "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
+ "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
+ "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
+ "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
+ "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
+ "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
+ "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
+ "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "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/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": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "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": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "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",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "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": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "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.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "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/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/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.32",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
+ "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+ "license": "MIT"
+ },
+ "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/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-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-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-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-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",
+ "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",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.361",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz",
+ "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "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/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.46",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz",
+ "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/reactflow": {
+ "version": "11.11.4",
+ "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
+ "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==",
+ "license": "MIT",
+ "dependencies": {
+ "@reactflow/background": "11.3.14",
+ "@reactflow/controls": "11.2.14",
+ "@reactflow/core": "11.11.4",
+ "@reactflow/minimap": "11.7.14",
+ "@reactflow/node-resizer": "2.2.14",
+ "@reactflow/node-toolbar": "1.3.14"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
+ "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.4",
+ "@rollup/rollup-android-arm64": "4.60.4",
+ "@rollup/rollup-darwin-arm64": "4.60.4",
+ "@rollup/rollup-darwin-x64": "4.60.4",
+ "@rollup/rollup-freebsd-arm64": "4.60.4",
+ "@rollup/rollup-freebsd-x64": "4.60.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.4",
+ "@rollup/rollup-linux-arm64-musl": "4.60.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.4",
+ "@rollup/rollup-linux-loong64-musl": "4.60.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-musl": "4.60.4",
+ "@rollup/rollup-openbsd-x64": "4.60.4",
+ "@rollup/rollup-openharmony-arm64": "4.60.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.4",
+ "@rollup/rollup-win32-x64-gnu": "4.60.4",
+ "@rollup/rollup-win32-x64-msvc": "4.60.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/testing_backend_ui/package.json b/testing_backend_ui/package.json
new file mode 100644
index 0000000..f81c30c
--- /dev/null
+++ b/testing_backend_ui/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "testing-backend-ui",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "reactflow": "^11.11.4"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.1",
+ "vite": "^5.4.10"
+ }
+}
\ No newline at end of file
diff --git a/testing_backend_ui/src/App.jsx b/testing_backend_ui/src/App.jsx
new file mode 100644
index 0000000..b6d4781
--- /dev/null
+++ b/testing_backend_ui/src/App.jsx
@@ -0,0 +1,994 @@
+import { useMemo, useRef, useState, useEffect } from 'react';
+import ReactFlow, {
+ Background,
+ Controls,
+ MarkerType,
+ MiniMap,
+ Position,
+ ReactFlowProvider,
+} from 'reactflow';
+import 'reactflow/dist/style.css';
+
+const BACKEND_WS_URL = import.meta.env.VITE_ORACLE_WS_URL ?? 'ws://localhost:8000/ws/analyze';
+// derive HTTP API base from websocket URL (fallback)
+const API_BASE = import.meta.env.VITE_API_BASE ?? BACKEND_WS_URL.replace(/^ws/, 'http').replace('/ws/analyze', '');
+const DEFAULT_REPO_URL = 'https://github.com/Project-XI/Project-EL';
+
+const STATUS_CLASS = {
+ idle: 'status-idle',
+ running: 'status-running',
+ done: 'status-done',
+ error: 'status-error',
+};
+
+const EVENT_CLASS = {
+ HANDOFF: 'event-handoff',
+ STATUS: 'event-status',
+ RESULT: 'event-result',
+ FLAG: 'event-flag',
+ ERROR: 'event-error',
+};
+
+const AGENT_META = {
+ gatekeeper: {
+ id: 'gatekeeper',
+ name: 'GATEKEEPER',
+ status: 'idle',
+ progress: 0,
+ lastEvent: 'Awaiting validation start',
+ durationMs: 0,
+ position: { x: 120, y: 380 },
+ },
+ oracle: {
+ id: 'oracle',
+ name: 'ORACLE',
+ status: 'idle',
+ progress: 0,
+ lastEvent: 'Awaiting repository analysis',
+ durationMs: 0,
+ position: { x: 300, y: 40 },
+ },
+ main: {
+ id: 'main',
+ name: 'MAIN VIVA',
+ status: 'idle',
+ progress: 0,
+ lastEvent: 'Awaiting viva handoff',
+ durationMs: 0,
+ position: { x: 300, y: 220 },
+ },
+ sentinel: {
+ id: 'sentinel',
+ name: 'SENTINEL',
+ status: 'idle',
+ progress: 0,
+ lastEvent: 'Awaiting oversight stream',
+ durationMs: 0,
+ position: { x: 520, y: 400 },
+ },
+};
+
+const DEFAULT_ALERTS = [
+ {
+ id: 'alt-1',
+ owner: 'SENTINEL',
+ type: 'tone shift',
+ severity: 'high',
+ summary: 'Rapid tone instability during follow-up Q2.',
+ },
+ {
+ id: 'alt-2',
+ owner: 'SENTINEL',
+ type: 'gaze anomaly',
+ severity: 'medium',
+ summary: 'Repeated gaze divergence during answer window.',
+ },
+ {
+ id: 'alt-3',
+ owner: 'GATEKEEPER',
+ type: 'identity mismatch',
+ severity: 'critical',
+ summary: 'Voice-print variance exceeded allowed tolerance.',
+ },
+ {
+ id: 'alt-4',
+ owner: 'GATEKEEPER',
+ type: 'session breach',
+ severity: 'low',
+ summary: 'Unexpected short disconnect recovered automatically.',
+ },
+];
+
+const AGENT_SEQUENCE = ['GATEKEEPER', 'ORACLE', 'MAIN VIVA', 'SENTINEL'];
+const EDGE_BY_SOURCE = {
+ GATEKEEPER: 'e-gatekeeper-oracle',
+ ORACLE: 'e-oracle-main',
+ 'MAIN VIVA': 'e-main-sentinel',
+ SENTINEL: 'e-main-sentinel',
+};
+
+function normalizeKey(value) {
+ return String(value ?? '')
+ .toUpperCase()
+ .replace(/\s+/g, '_')
+ .replace(/[^A-Z0-9_]/g, '');
+}
+
+function createInitialAgentState() {
+ return Object.fromEntries(
+ Object.values(AGENT_META).map((agent) => [
+ agent.id,
+ {
+ name: agent.name,
+ status: agent.status,
+ progress: agent.progress,
+ lastEvent: agent.lastEvent,
+ durationMs: agent.durationMs,
+ },
+ ])
+ );
+}
+
+function AgentNode({ data }) {
+ return (
+
+
{data.name}
+
{data.status.toUpperCase()}
+
+ Progress
+ {data.progress}%
+
+
+
{data.lastEvent}
+
Last op: {data.durationMs}ms
+
+ );
+}
+
+function buildNodes(agentState) {
+ return Object.values(AGENT_META).map((agent) => ({
+ id: agent.id,
+ type: 'agentNode',
+ position: agent.position,
+ sourcePosition: Position.Bottom,
+ targetPosition: Position.Top,
+ data: {
+ name: agent.name,
+ status: agentState[agent.id]?.status ?? agent.status,
+ progress: agentState[agent.id]?.progress ?? 0,
+ lastEvent: agentState[agent.id]?.lastEvent ?? agent.lastEvent,
+ durationMs: agentState[agent.id]?.durationMs ?? agent.durationMs,
+ },
+ }));
+}
+
+function buildEdges(activeEdgeIds) {
+ return [
+ {
+ id: 'e-gatekeeper-oracle',
+ source: 'gatekeeper',
+ target: 'oracle',
+ label: 'HANDOFF',
+ markerEnd: { type: MarkerType.ArrowClosed, width: 18, height: 18 },
+ animated: activeEdgeIds.includes('e-gatekeeper-oracle'),
+ },
+ {
+ id: 'e-oracle-main',
+ source: 'oracle',
+ target: 'main',
+ label: 'RESULT',
+ markerEnd: { type: MarkerType.ArrowClosed, width: 18, height: 18 },
+ animated: activeEdgeIds.includes('e-oracle-main'),
+ },
+ {
+ id: 'e-main-sentinel',
+ source: 'main',
+ target: 'sentinel',
+ label: 'STATUS/FLAG',
+ markerEnd: { type: MarkerType.ArrowClosed, width: 18, height: 18 },
+ animated: activeEdgeIds.includes('e-main-sentinel'),
+ },
+ ];
+}
+
+function inferSourceAgent(message = '') {
+ if (message.includes('[Gatekeeper]')) return 'GATEKEEPER';
+ if (message.includes('[Oracle]')) return 'ORACLE';
+ if (message.includes('[MainAgent]')) return 'MAIN VIVA';
+ if (message.includes('[Sentinel]')) return 'SENTINEL';
+ return 'MAIN VIVA';
+}
+
+function inferEventType(logType = 'info', message = '') {
+ const text = `${logType} ${message}`.toLowerCase();
+ if (text.includes('error') || text.includes('failed') || text.includes('rejected') || text.includes('timeout')) {
+ return 'ERROR';
+ }
+ if (text.includes('flag') || text.includes('mismatch') || text.includes('contradiction') || text.includes('breach') || text.includes('risk')) {
+ return 'FLAG';
+ }
+ if (text.includes('complete') || text.includes('verified') || text.includes('success') || text.includes('result')) {
+ return 'RESULT';
+ }
+ if (text.includes('started') || text.includes('parsing') || text.includes('cloning') || text.includes('building') || text.includes('analyzing') || text.includes('detecting')) {
+ return 'HANDOFF';
+ }
+ return 'STATUS';
+}
+
+function inferTargetAgent(sourceAgent, eventType, message = '') {
+ if (sourceAgent === 'GATEKEEPER') return eventType === 'ERROR' || message.toLowerCase().includes('rejected') ? 'GATEKEEPER' : 'ORACLE';
+ if (sourceAgent === 'ORACLE') return 'MAIN VIVA';
+ if (sourceAgent === 'MAIN VIVA') return 'SENTINEL';
+ if (sourceAgent === 'SENTINEL') return 'MAIN VIVA';
+ return 'MAIN VIVA';
+}
+
+function inferProgress(sourceAgent, message = '', eventType = 'STATUS') {
+ const text = message.toLowerCase();
+
+ if (sourceAgent === 'GATEKEEPER') {
+ if (text.includes('verified')) return { status: 'done', progress: 100 };
+ if (text.includes('rejected') || eventType === 'ERROR') return { status: 'error', progress: 100 };
+ if (text.includes('started')) return { status: 'running', progress: 20 };
+ return { status: 'running', progress: 60 };
+ }
+
+ if (sourceAgent === 'ORACLE') {
+ if (text.includes('submission intelligence complete') || text.includes('complete')) return { status: 'done', progress: 100 };
+ if (text.includes('parsing')) return { status: 'running', progress: 15 };
+ if (text.includes('cloning')) return { status: 'running', progress: 30 };
+ if (text.includes('detecting')) return { status: 'running', progress: 45 };
+ if (text.includes('building execution graph')) return { status: 'running', progress: 60 };
+ if (text.includes('extracting observable')) return { status: 'running', progress: 70 };
+ if (text.includes('analyzing failure')) return { status: 'running', progress: 82 };
+ if (text.includes('generating viva')) return { status: 'running', progress: 92 };
+ return { status: 'running', progress: 50 };
+ }
+
+ if (sourceAgent === 'MAIN VIVA') {
+ if (text.includes('analysis complete') || text.includes('voice_viva.completed')) return { status: 'done', progress: 100 };
+ if (text.includes('voice_viva.started')) return { status: 'running', progress: 10 };
+ if (text.includes('question.playback.started')) return { status: 'running', progress: 25 };
+ if (text.includes('turn.finalized')) return { status: 'running', progress: 50 };
+ if (text.includes('turn.evaluated')) return { status: 'running', progress: 70 };
+ if (text.includes('topic.coverage.updated')) return { status: 'running', progress: 85 };
+ if (eventType === 'ERROR') return { status: 'error', progress: 100 };
+ return { status: 'running', progress: 40 };
+ }
+
+ if (sourceAgent === 'SENTINEL') {
+ if (eventType === 'ERROR') return { status: 'error', progress: 100 };
+ if (text.includes('complete')) return { status: 'done', progress: 100 };
+ if (text.includes('placeholder')) return { status: 'running', progress: 15 };
+ if (text.includes('flag')) return { status: 'running', progress: 60 };
+ return { status: 'running', progress: 35 };
+ }
+
+ return { status: 'running', progress: 50 };
+}
+
+function mapAgentNameToId(name) {
+ const key = String(name || '').toUpperCase();
+ if (key.includes('GATEKEEPER')) return 'gatekeeper';
+ if (key.includes('ORACLE')) return 'oracle';
+ if (key.includes('MAIN') || key.includes('VIVA')) return 'main';
+ if (key.includes('SENTINEL')) return 'sentinel';
+ return 'main';
+}
+
+function buildTimelineBlocks(events) {
+ if (events.length === 0) {
+ return AGENT_SEQUENCE.map((agent, index) => ({
+ agent,
+ startMs: 0,
+ endMs: 0,
+ label: 'Waiting',
+ widthPct: 12,
+ leftPct: index * 18,
+ }));
+ }
+
+ const start = Math.min(...events.map((event) => event.duration_ms));
+ const end = Math.max(...events.map((event) => event.duration_ms));
+ const span = Math.max(end - start, 1);
+
+ return AGENT_SEQUENCE.map((agent, index) => {
+ const agentEvents = events.filter((event) => event.source_agent === agent || event.target_agent === agent);
+ if (agentEvents.length === 0) {
+ return {
+ agent,
+ startMs: start,
+ endMs: start,
+ label: 'Pending',
+ widthPct: 12,
+ leftPct: index * 18,
+ };
+ }
+
+ const agentStart = Math.min(...agentEvents.map((event) => event.duration_ms));
+ const agentEnd = Math.max(...agentEvents.map((event) => event.duration_ms));
+ return {
+ agent,
+ startMs: agentStart,
+ endMs: agentEnd,
+ label: `${agentEvents[0].event_type} window`,
+ leftPct: ((agentStart - start) / span) * 100,
+ widthPct: Math.max(((agentEnd - agentStart) / span) * 100, 10),
+ };
+ });
+}
+
+function Timeline({ blocks, onSelectWindow }) {
+ return (
+
+ {blocks.map((block) => (
+
+
{block.agent}
+
+
+
+
+ ))}
+
+ );
+}
+
+function formatTime(timestamp) {
+ return new Date(timestamp).toLocaleTimeString([], { hour12: false });
+}
+
+function payloadPreview(payload) {
+ if (!payload) return '{}';
+ if (typeof payload === 'string') return payload;
+ try {
+ return JSON.stringify(payload, null, 2);
+ } catch {
+ return String(payload);
+ }
+}
+
+function createAlertId(alert) {
+ return `${normalizeKey(alert.owner)}_${normalizeKey(alert.type)}_${normalizeKey(alert.severity)}`;
+}
+
+function App() {
+ const [repoUrl, setRepoUrl] = useState(DEFAULT_REPO_URL);
+ const [backendUrl] = useState(BACKEND_WS_URL);
+ const [rollNumber, setRollNumber] = useState('');
+ const [connectionState, setConnectionState] = useState('idle');
+ const connectionStateRef = useRef('idle');
+ const [sessionId, setSessionId] = useState('idle');
+ const sessionIdRef = useRef('idle');
+ const [analysisData, setAnalysisData] = useState(null);
+ const [pendingAlerts, setPendingAlerts] = useState([]);
+ const [agentState, setAgentState] = useState(createInitialAgentState());
+ const [liveEvents, setLiveEvents] = useState([]);
+ const [selectedAgent, setSelectedAgent] = useState('ALL');
+ const [selectedSession, setSelectedSession] = useState('ALL');
+ const [selectedRange, setSelectedRange] = useState(null);
+ const [dismissedAlerts, setDismissedAlerts] = useState([]);
+ const [highlightEdgeId, setHighlightEdgeId] = useState('e-gatekeeper-oracle');
+ const [statusMessage, setStatusMessage] = useState('Connect to the backend to stream live agent data.');
+ const [activeSection, setActiveSection] = useState('node-graph');
+ const wsRef = useRef(null);
+ const startTimeRef = useRef(0);
+ const eventCounterRef = useRef(0);
+
+ const nodes = useMemo(() => buildNodes(agentState), [agentState]);
+ const edges = useMemo(() => buildEdges([highlightEdgeId]), [highlightEdgeId]);
+ const sessionOptions = useMemo(() => ['ALL', ...new Set(liveEvents.map((event) => event.session_id))], [liveEvents]);
+ const timelineBlocks = useMemo(() => buildTimelineBlocks(liveEvents), [liveEvents]);
+
+ const filteredEvents = useMemo(() => {
+ return liveEvents.filter((event) => {
+ const byAgent =
+ selectedAgent === 'ALL' ||
+ event.source_agent === selectedAgent ||
+ event.target_agent === selectedAgent;
+ const bySession = selectedSession === 'ALL' || event.session_id === selectedSession;
+ const byRange =
+ !selectedRange ||
+ (event.duration_ms >= selectedRange.startMs && event.duration_ms <= selectedRange.endMs);
+ return byAgent && bySession && byRange;
+ });
+ }, [liveEvents, selectedAgent, selectedSession, selectedRange]);
+
+ const visibleAlerts = useMemo(() => {
+ const runtimeAlerts = [];
+
+ if (analysisData?.runtime_risks?.length) {
+ analysisData.runtime_risks.forEach((risk, index) => {
+ runtimeAlerts.push({
+ id: `risk-${index}-${normalizeKey(risk.value)}`,
+ owner: 'SENTINEL',
+ type: risk.value,
+ severity: String(risk.severity || 'medium').toLowerCase(),
+ summary: risk.evidence?.[0] || 'Runtime risk detected from analysis result.',
+ });
+ });
+ }
+
+ if (analysisData?.inconsistencies?.length) {
+ analysisData.inconsistencies.forEach((flag, index) => {
+ runtimeAlerts.push({
+ id: `flag-${index}-${normalizeKey(flag.issue)}`,
+ owner: 'SENTINEL',
+ type: flag.issue,
+ severity: String(flag.severity || 'medium').toLowerCase(),
+ summary: flag.evidence?.[0] || 'Analysis inconsistency detected.',
+ });
+ });
+ }
+
+ if (analysisData?.gatekeeper_status === 'rejected') {
+ runtimeAlerts.push({
+ id: 'gatekeeper-rejected',
+ owner: 'GATEKEEPER',
+ type: 'identity mismatch',
+ severity: 'critical',
+ summary: analysisData.gatekeeper_reason || 'Submission rejected by Gatekeeper.',
+ });
+ }
+
+ const merged = [...DEFAULT_ALERTS, ...pendingAlerts, ...runtimeAlerts];
+ const unique = merged.filter((alert, index, array) => index === array.findIndex((candidate) => candidate.id === alert.id));
+ return unique.filter((alert) => !dismissedAlerts.includes(alert.id));
+ }, [analysisData, dismissedAlerts, pendingAlerts]);
+
+ async function fetchPendingAlerts() {
+ try {
+ const res = await fetch(`${API_BASE}/face/pending-alerts`);
+ if (!res.ok) return;
+ const payload = await res.json();
+ // normalize to array of alerts
+ const items = Array.isArray(payload) ? payload : payload.results || [];
+ const normalized = items.map((a, i) => ({
+ id: a.conflict_id || a.id || `pending-${i}`,
+ owner: a.owner || 'SENTINEL',
+ type: a.type || a.issue || 'unknown',
+ severity: (a.severity || 'medium').toLowerCase(),
+ summary: a.summary || a.evidence || JSON.stringify(a).slice(0, 120),
+ }));
+ setPendingAlerts(normalized);
+ } catch (err) {
+ // ignore
+ }
+ }
+
+ // fetch pending alerts when session starts
+ useEffect(() => {
+ if (sessionId && sessionId !== 'idle') {
+ fetchPendingAlerts();
+ }
+ }, [sessionId]);
+
+ function setConnection(nextState) {
+ connectionStateRef.current = nextState;
+ setConnectionState(nextState);
+ }
+
+ function setSession(nextSession) {
+ sessionIdRef.current = nextSession;
+ setSessionId(nextSession);
+ }
+
+ function closeWebSocket() {
+ if (wsRef.current) {
+ try {
+ wsRef.current.close();
+ } catch {
+ // noop
+ }
+ wsRef.current = null;
+ }
+ }
+
+ function resetRunState(nextSessionId) {
+ setSession(nextSessionId);
+ setConnection('connecting');
+ setAnalysisData(null);
+ setLiveEvents([]);
+ setAgentState(createInitialAgentState());
+ setDismissedAlerts([]);
+ setSelectedAgent('ALL');
+ setSelectedSession('ALL');
+ setSelectedRange(null);
+ setHighlightEdgeId('e-gatekeeper-oracle');
+ setStatusMessage('Connecting to backend websocket...');
+ startTimeRef.current = Date.now();
+ eventCounterRef.current = 0;
+ }
+
+ function appendEvent(message, logType, payload, explicitSource = null) {
+ const timestamp = new Date().toISOString();
+ const elapsedMs = Math.max(Date.now() - startTimeRef.current, 0);
+ const sourceAgent = explicitSource || inferSourceAgent(message);
+ const eventType = inferEventType(logType, message);
+ const targetAgent = inferTargetAgent(sourceAgent, eventType, message);
+
+ const event = {
+ event_id: `evt-${String(++eventCounterRef.current).padStart(3, '0')}`,
+ timestamp,
+ source_agent: sourceAgent,
+ target_agent: targetAgent,
+ event_type: eventType,
+ session_id: sessionIdRef.current,
+ payload,
+ duration_ms: elapsedMs,
+ };
+
+ setLiveEvents((prev) => [...prev, event]);
+ setHighlightEdgeId(EDGE_BY_SOURCE[sourceAgent] || 'e-main-sentinel');
+ setStatusMessage(message);
+
+ setAgentState((prev) => {
+ const next = { ...prev };
+ const progress = inferProgress(sourceAgent, message, eventType);
+
+ const updateSource = (key) => {
+ next[key] = {
+ ...next[key],
+ ...progress,
+ lastEvent: message,
+ durationMs: elapsedMs,
+ };
+ };
+
+ if (sourceAgent === 'GATEKEEPER') {
+ updateSource('gatekeeper');
+ if (progress.status === 'done' && next.oracle.status === 'idle') {
+ next.oracle = {
+ ...next.oracle,
+ status: 'running',
+ progress: 10,
+ lastEvent: 'Waiting for ORACLE analysis to begin',
+ durationMs: elapsedMs,
+ };
+ }
+ }
+
+ if (sourceAgent === 'ORACLE') {
+ updateSource('oracle');
+ if (progress.status === 'done' && next.main.status === 'idle') {
+ next.main = {
+ ...next.main,
+ status: 'running',
+ progress: 10,
+ lastEvent: 'Waiting for MAIN VIVA handoff',
+ durationMs: elapsedMs,
+ };
+ }
+ }
+
+ if (sourceAgent === 'MAIN VIVA') {
+ updateSource('main');
+ if (progress.status === 'done' && next.sentinel.status === 'idle') {
+ next.sentinel = {
+ ...next.sentinel,
+ status: 'running',
+ progress: 15,
+ lastEvent: 'Waiting for SENTINEL oversight',
+ durationMs: elapsedMs,
+ };
+ }
+ }
+
+ if (sourceAgent === 'SENTINEL') {
+ updateSource('sentinel');
+ }
+
+ return next;
+ });
+
+ return event;
+ }
+
+ function startAnalysis() {
+ if (!repoUrl) {
+ setStatusMessage('Enter a repository URL first.');
+ return;
+ }
+
+ closeWebSocket();
+ const nextSessionId = `session-${Date.now()}`;
+ resetRunState(nextSessionId);
+
+ const socket = new WebSocket(backendUrl);
+ wsRef.current = socket;
+
+ socket.onopen = () => {
+ setConnection('running');
+ setStatusMessage('Backend connected. Starting GATEKEEPER → ORACLE → MAIN VIVA → SENTINEL pipeline.');
+ socket.send(
+ JSON.stringify({
+ repo_url: repoUrl,
+ report_path: null,
+ enable_viva: true,
+ enable_debug: true,
+ generate_report: false,
+ // optional roll_number for Gatekeeper
+ roll_number: rollNumber || undefined,
+ })
+ );
+
+ appendEvent(
+ '[MainAgent] Live analysis session opened.',
+ 'info',
+ {
+ repo_url: repoUrl,
+ backend_url: backendUrl,
+ },
+ 'MAIN VIVA'
+ );
+ };
+
+ socket.onmessage = (rawEvent) => {
+ let data;
+ try {
+ data = JSON.parse(rawEvent.data);
+ } catch {
+ return;
+ }
+
+ // If backend sends structured PlatformEvent or 'event' messages
+ if (data.event_type || data.type === 'event' || data.agent_name || data.source_agent) {
+ const sourceName = data.agent_name || data.source_agent || data.agent || (data.payload && data.payload.source) || null;
+ const sourceAgent = sourceName ? sourceName : inferSourceAgent(String(data.message || ''));
+ const eventType = data.event_type || data.type || 'event';
+ const session = data.session_id || data.session || sessionIdRef.current;
+ const timestamp = data.timestamp || new Date().toISOString();
+ const duration_ms = data.duration_ms || data.durationMs || Math.max(Date.now() - startTimeRef.current, 0);
+ const payload = data.payload || data.data || data.message || {};
+
+ const evt = {
+ event_id: data.event_id || `evt-${Date.now()}`,
+ timestamp,
+ source_agent: sourceAgent,
+ target_agent: data.target_agent || data.to_agent || inferTargetAgent(sourceAgent, eventType, String(payload?.message || '')),
+ event_type: eventType,
+ session_id: session,
+ payload,
+ duration_ms,
+ };
+
+ setLiveEvents((prev) => [...prev, evt]);
+
+ // update agent progress if provided in payload or if event_type suggests progress
+ if ((String(eventType).toLowerCase().includes('progress')) || payload?.progress !== undefined || payload?.status) {
+ const agentKey = mapAgentNameToId(evt.source_agent);
+ const p = typeof payload.progress === 'number' ? payload.progress : undefined;
+ const status = payload.status || undefined;
+ setAgentState((prev) => {
+ const next = { ...prev };
+ if (agentKey && next[agentKey]) {
+ next[agentKey] = {
+ ...next[agentKey],
+ progress: p !== undefined ? p : next[agentKey].progress,
+ status: status || next[agentKey].status,
+ lastEvent: typeof payload === 'string' ? payload : (payload.summary || JSON.stringify(payload).slice(0, 120)),
+ durationMs: duration_ms,
+ };
+ }
+ return next;
+ });
+ }
+
+ // highlight handoff edges
+ if (String(eventType).toLowerCase().includes('handoff')) {
+ setHighlightEdgeId(EDGE_BY_SOURCE[evt.source_agent] || 'e-main-sentinel');
+ }
+
+ return;
+ }
+
+ // legacy log messages
+ if (data.type === 'log') {
+ const message = String(data.message || '');
+ const logType = String(data.log_type || data.type || 'info');
+ const source = inferSourceAgent(message);
+ const event = appendEvent(message, logType, { message, log_type: logType }, source);
+
+ if (event.event_type === 'ERROR') {
+ setConnection('error');
+ }
+ if (message.toLowerCase().includes('analysis complete')) {
+ setConnection('done');
+ }
+ return;
+ }
+
+ if (data.type === 'result') {
+ const payload = data.data || {};
+ setAnalysisData(payload);
+
+ appendEvent(
+ '[Oracle] Structured result received from backend.',
+ 'success',
+ {
+ project_name: payload.project_name?.value,
+ backend_framework: payload.backend_framework?.value,
+ architecture_pattern: payload.architecture_pattern?.value,
+ viva_targets: Array.isArray(payload.implementation_viva_targets)
+ ? payload.implementation_viva_targets.length
+ : Array.isArray(payload.viva_intelligence_targets)
+ ? payload.viva_intelligence_targets.length
+ : 0,
+ },
+ 'ORACLE'
+ );
+
+ setAgentState((prev) => ({
+ gatekeeper: {
+ ...prev.gatekeeper,
+ status: prev.gatekeeper.status === 'error' ? 'error' : 'done',
+ progress: 100,
+ lastEvent: prev.gatekeeper.lastEvent,
+ durationMs: prev.gatekeeper.durationMs,
+ },
+ oracle: {
+ ...prev.oracle,
+ status: 'done',
+ progress: 100,
+ lastEvent: payload.backend_framework?.value
+ ? `Backend: ${payload.backend_framework.value}`
+ : 'Submission intelligence complete',
+ durationMs: prev.oracle.durationMs,
+ },
+ main: {
+ ...prev.main,
+ status: 'done',
+ progress: 100,
+ lastEvent: payload.viva_intelligence_targets?.length
+ ? `${payload.viva_intelligence_targets.length} viva targets prepared`
+ : 'Main viva completed',
+ durationMs: prev.main.durationMs,
+ },
+ sentinel: {
+ ...prev.sentinel,
+ status: prev.sentinel.status === 'error' ? 'error' : 'done',
+ progress: 100,
+ lastEvent: payload.runtime_risks?.length
+ ? `${payload.runtime_risks.length} runtime risks under review`
+ : 'Oversight complete',
+ durationMs: prev.sentinel.durationMs,
+ },
+ }));
+
+ setHighlightEdgeId('e-main-sentinel');
+ setConnection('done');
+ }
+ };
+
+ socket.onerror = () => {
+ setConnection('error');
+ setStatusMessage('WebSocket connection failed. Make sure the backend is running on port 8000.');
+ setAgentState((prev) => ({
+ ...prev,
+ oracle: { ...prev.oracle, status: 'error' },
+ }));
+ };
+
+ socket.onclose = () => {
+ wsRef.current = null;
+ if (connectionStateRef.current !== 'done' && connectionStateRef.current !== 'error') {
+ setConnection('idle');
+ }
+ };
+ }
+
+ function onTimelineSelect(agent, startMs, endMs) {
+ setSelectedAgent(agent);
+ setSelectedRange({ startMs, endMs });
+ setHighlightEdgeId(EDGE_BY_SOURCE[agent] || 'e-main-sentinel');
+ setStatusMessage(`Timeline window filtered for ${agent} (${startMs}ms → ${endMs}ms).`);
+ }
+
+ function dismissAlert(id) {
+ // attempt to resolve on backend, then mark dismissed locally
+ (async () => {
+ try {
+ await fetch(`${API_BASE}/face/resolve-alert`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ conflict_id: id, approved: false }),
+ });
+ } catch (err) {
+ // ignore network errors; still dismiss locally
+ }
+ setDismissedAlerts((prev) => [...prev, id]);
+ })();
+ }
+
+ function scrollToSection(id) {
+ const target = document.getElementById(id);
+ if (target) {
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ setActiveSection(id);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
1. Agent Topology Graph
+
React Flow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
2. Agent Progress Overview
+
Four-agent completion cards
+
+
+ {Object.values(agentState).map((agent) => (
+
+
+ {agent.name}
+ {agent.progress}%
+
+ {agent.status.toUpperCase()}
+
+ {agent.lastEvent}
+ Last operation: {agent.durationMs}ms
+
+ ))}
+
+
+
+
+
+
+
3. Session Timeline Panel
+
Gantt Style
+
+
+
+
+
+
+
4. Live Event Feed
+
Chronological Event Stream
+
+
+
+
+
+
+
+
+ {filteredEvents.map((event) => (
+
+
+ {formatTime(event.timestamp)}
+ {event.event_type}
+ {event.session_id}
+
+
+ {event.source_agent} → {event.target_agent}
+
+
+ event_id: {event.event_id}
+ duration_ms: {event.duration_ms}
+
+ {payloadPreview(event.payload)}
+
+ ))}
+ {filteredEvents.length === 0 &&
No events match the current filter.
}
+
+
+
+
+
+
+
+
5. Alert Cards Panel
+
SENTINEL + GATEKEEPER
+
+
+ {visibleAlerts.length === 0 &&
No active alerts.
}
+ {visibleAlerts.map((alert) => (
+
+
+ {alert.owner}
+ {alert.severity.toUpperCase()}
+
+ {alert.type}
+ {alert.summary}
+
+
+ ))}
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/testing_backend_ui/src/main.jsx b/testing_backend_ui/src/main.jsx
new file mode 100644
index 0000000..d106f4f
--- /dev/null
+++ b/testing_backend_ui/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './styles.css';
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+);
diff --git a/testing_backend_ui/src/styles.css b/testing_backend_ui/src/styles.css
new file mode 100644
index 0000000..732279a
--- /dev/null
+++ b/testing_backend_ui/src/styles.css
@@ -0,0 +1,531 @@
+:root {
+ --highlight: #E8E3DC;
+ --text-primary: #1C1A17;
+ --text-secondary: #4A4740;
+ --text-muted: #8A8580;
+ --accent: #2B2825;
+ --rule: #D4D0CA;
+
+ --status-idle: #3A78C2;
+ --status-running: #D3A200;
+ --status-done: #2D9A48;
+ --status-error: #C32E2E;
+
+ --font-serif: 'Cormorant Garamond', serif;
+ --font-sans: 'DM Sans', sans-serif;
+ --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ margin: 0;
+ height: 100%;
+ font-family: var(--font-sans);
+ color: var(--text-primary);
+ background: #F5F2EE;
+ overflow: auto;
+}
+
+.dashboard-shell {
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 0.9rem 1rem;
+ border-bottom: 1px solid var(--rule);
+ background: #FAFAF8;
+ position: sticky;
+ top: 0;
+ z-index: 20;
+}
+
+.brand {
+ font-family: var(--font-serif);
+ font-size: 1.5rem;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+}
+
+.menu {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.menu-item {
+ border: none;
+ background: transparent;
+ color: var(--text-secondary);
+ font-family: var(--font-sans);
+ font-weight: 600;
+ font-size: 0.78rem;
+ letter-spacing: 0.08em;
+ padding: 0.4rem 0.5rem;
+ cursor: pointer;
+}
+
+.menu-item.active {
+ color: var(--accent);
+}
+
+.menu-separator {
+ width: 1px;
+ height: 16px;
+ background: var(--rule);
+}
+
+.dashboard-stack {
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.control-strip {
+ border: 1px solid var(--rule);
+ background: #FAFAF8;
+ padding: 0.85rem 0.95rem;
+ display: grid;
+ grid-template-columns: 1.2fr 1fr auto;
+ gap: 0.8rem;
+ align-items: end;
+}
+
+.control-field {
+ display: grid;
+ gap: 0.35rem;
+ font-size: 0.78rem;
+ color: var(--text-secondary);
+}
+
+.control-field input {
+ border: 1px solid var(--rule);
+ background: #fff;
+ color: var(--text-primary);
+ padding: 0.55rem 0.7rem;
+ font-family: var(--font-mono);
+}
+
+.control-summary {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem;
+ color: var(--text-muted);
+ font-size: 0.76rem;
+}
+
+.control-summary span {
+ border: 1px solid var(--rule);
+ padding: 0.35rem 0.55rem;
+ background: #fff;
+}
+
+.control-button {
+ border: 1px solid var(--accent);
+ background: var(--accent);
+ color: #FAFAF8;
+ padding: 0.7rem 1rem;
+ font-family: var(--font-sans);
+ font-weight: 700;
+ letter-spacing: 0.05em;
+ cursor: pointer;
+}
+
+.control-button:hover {
+ background: #1C1A17;
+}
+
+.split-panels {
+ display: grid;
+ grid-template-columns: 1fr 1.3fr;
+ gap: 1rem;
+}
+
+.panel {
+ border: 1px solid var(--rule);
+ background: #FAFAF8;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.panel-header {
+ padding: 0.75rem 0.9rem;
+ border-bottom: 1px solid var(--rule);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.panel-header h2 {
+ margin: 0;
+ font-size: 1rem;
+ font-family: var(--font-serif);
+}
+
+.panel-meta {
+ color: var(--text-muted);
+ font-size: 0.78rem;
+}
+
+.panel-body {
+ padding: 0.8rem;
+ overflow: auto;
+}
+
+.panel-progress .panel-body {
+ padding: 0.9rem;
+}
+
+.panel-graph {
+ min-height: 520px;
+}
+
+.graph-body {
+ padding: 0;
+}
+
+.progress-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 0.8rem;
+}
+
+.progress-card {
+ border: 1px solid var(--rule);
+ background: #fff;
+ padding: 0.75rem;
+}
+
+.progress-card-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.8rem;
+ margin-bottom: 0.35rem;
+}
+
+.agent-node {
+ min-width: 220px;
+ max-width: 260px;
+ border: 2px solid var(--rule);
+ background: #fff;
+ border-radius: 8px;
+ padding: 0.6rem 0.7rem;
+ box-shadow: 0 2px 10px rgba(28, 26, 23, 0.08);
+}
+
+.agent-node-name {
+ font-family: var(--font-serif);
+ font-size: 1.05rem;
+ margin-bottom: 0.1rem;
+}
+
+.agent-node-status {
+ font-size: 0.7rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ margin-bottom: 0.4rem;
+}
+
+.agent-node-progress {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.68rem;
+ color: var(--text-muted);
+ margin-bottom: 0.25rem;
+}
+
+.agent-node-bar,
+.progress-bar-shell {
+ height: 6px;
+ background: var(--rule);
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 0.5rem;
+}
+
+.agent-node-bar-fill,
+.progress-bar-fill {
+ position: absolute;
+ inset: 0 auto 0 0;
+ background: var(--accent);
+}
+
+.agent-node-last {
+ color: var(--text-secondary);
+ font-size: 0.78rem;
+ line-height: 1.3;
+ margin-bottom: 0.35rem;
+}
+
+.agent-node-time {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ color: var(--text-muted);
+}
+
+.status-idle {
+ border-color: var(--status-idle);
+}
+
+.status-running {
+ border-color: var(--status-running);
+}
+
+.status-done {
+ border-color: var(--status-done);
+}
+
+.status-error {
+ border-color: var(--status-error);
+}
+
+.timeline-grid {
+ display: grid;
+ gap: 0.65rem;
+}
+
+.timeline-row {
+ display: grid;
+ grid-template-columns: 110px 1fr;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.timeline-agent {
+ font-size: 0.75rem;
+ font-weight: 700;
+ color: var(--text-secondary);
+}
+
+.timeline-track {
+ position: relative;
+ height: 34px;
+ border: 1px solid var(--rule);
+ background: #F5F2EE;
+}
+
+.timeline-block {
+ position: absolute;
+ top: 3px;
+ height: 26px;
+ border: 1px solid var(--accent);
+ background: var(--highlight);
+ color: var(--text-primary);
+ font-size: 0.7rem;
+ cursor: pointer;
+ font-family: var(--font-sans);
+}
+
+.window-message {
+ margin: 0.8rem 0 0;
+ font-size: 0.8rem;
+ color: var(--text-muted);
+}
+
+.filters-row {
+ display: flex;
+ gap: 0.8rem;
+ margin-bottom: 0.7rem;
+}
+
+.filters-row label {
+ display: grid;
+ gap: 0.25rem;
+ font-size: 0.78rem;
+ color: var(--text-secondary);
+}
+
+.filters-row select {
+ border: 1px solid var(--rule);
+ background: #fff;
+ color: var(--text-primary);
+ padding: 0.3rem 0.45rem;
+ font-family: var(--font-sans);
+}
+
+.event-feed {
+ display: grid;
+ gap: 0.55rem;
+}
+
+.event-card {
+ border: 1px solid var(--rule);
+ background: #fff;
+ padding: 0.55rem;
+}
+
+.event-head {
+ display: flex;
+ gap: 0.45rem;
+ align-items: center;
+ font-size: 0.72rem;
+ color: var(--text-muted);
+}
+
+.event-type {
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.event-session {
+ margin-left: auto;
+ font-family: var(--font-mono);
+}
+
+.event-route {
+ margin-top: 0.3rem;
+ font-size: 0.82rem;
+ font-weight: 600;
+}
+
+.event-schema {
+ margin-top: 0.3rem;
+ display: flex;
+ gap: 0.7rem;
+ font-family: var(--font-mono);
+ font-size: 0.68rem;
+ color: var(--text-muted);
+}
+
+.event-payload {
+ margin: 0.38rem 0 0;
+ padding: 0.4rem;
+ border: 1px solid var(--rule);
+ background: #F5F2EE;
+ font-size: 0.66rem;
+ font-family: var(--font-mono);
+ overflow: auto;
+}
+
+.event-handoff {
+ border-left: 4px solid #4B74B4;
+}
+
+.event-status {
+ border-left: 4px solid #9A7C35;
+}
+
+.event-result {
+ border-left: 4px solid #2D9A48;
+}
+
+.event-flag {
+ border-left: 4px solid #AD6F1A;
+}
+
+.event-error {
+ border-left: 4px solid #C32E2E;
+}
+
+.alert-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.8rem;
+}
+
+.alert-card {
+ border: 1px solid var(--rule);
+ background: #fff;
+ padding: 0.7rem;
+}
+
+.alert-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.35rem;
+ font-size: 0.78rem;
+}
+
+.alert-type {
+ font-family: var(--font-serif);
+ font-size: 1.06rem;
+ margin-bottom: 0.35rem;
+}
+
+.alert-card p {
+ margin: 0 0 0.6rem;
+ color: var(--text-secondary);
+ font-size: 0.84rem;
+}
+
+.alert-card button {
+ border: 1px solid var(--rule);
+ background: #F5F2EE;
+ color: var(--text-primary);
+ font-family: var(--font-sans);
+ font-size: 0.75rem;
+ padding: 0.25rem 0.6rem;
+ cursor: pointer;
+}
+
+.severity-low {
+ border-left: 4px solid #4A7D6A;
+}
+
+.severity-medium {
+ border-left: 4px solid #B88A2C;
+}
+
+.severity-high {
+ border-left: 4px solid #C26C1A;
+}
+
+.severity-critical {
+ border-left: 4px solid #C32E2E;
+}
+
+@media (max-width: 1200px) {
+ .control-strip {
+ grid-template-columns: 1fr;
+ }
+
+ .split-panels {
+ grid-template-columns: 1fr;
+ }
+
+ .progress-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .timeline-row {
+ grid-template-columns: 88px 1fr;
+ }
+
+ .alert-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .filters-row {
+ flex-direction: column;
+ }
+}
+
+@media (max-width: 760px) {
+ .progress-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .menu {
+ gap: 0.35rem;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ }
+
+ .menu-separator {
+ display: none;
+ }
+}
diff --git a/testing_backend_ui/vite.config.js b/testing_backend_ui/vite.config.js
new file mode 100644
index 0000000..0466183
--- /dev/null
+++ b/testing_backend_ui/vite.config.js
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+});
From 96849596f12abace2cfa42614e9cb12436dfd891 Mon Sep 17 00:00:00 2001
From: Rajkoli145 <2024.rajk@isu.ac.in>
Date: Tue, 2 Jun 2026 14:32:08 +0530
Subject: [PATCH 02/10] feat(models): add intelligence artifact, exam session,
and stage 7-8-9 pydantic models
- IntelligenceArtifact, VivaTarget, VivaSessionState, ExecutionNode, FailureScenario
- ExamSession, ExamSessionState, GatekeeperAdmissionDecision, SessionAuditEvent
- SentinelAlert, IntegritySignalType, EvaluationArtifact, ContradictionChainEntry
- Updated events.py and __init__.py to include new event types
---
backend/src/main.py | 174 +++--
backend/src/models/__init__.py | 39 +
backend/src/models/events.py | 50 ++
backend/src/models/exam_session.py | 107 +++
backend/src/models/intelligence_artifact.py | 196 +++++
backend/src/models/stage_7_8_9.py | 109 +++
frontend/app.js | 481 ------------
frontend/index.html | 306 ++++----
frontend/orb.js | 325 ++++++++
frontend/style.css | 808 +++++++-------------
test-commit.md | 2 +-
11 files changed, 1389 insertions(+), 1208 deletions(-)
create mode 100644 backend/src/models/exam_session.py
create mode 100644 backend/src/models/intelligence_artifact.py
create mode 100644 backend/src/models/stage_7_8_9.py
delete mode 100644 frontend/app.js
diff --git a/backend/src/main.py b/backend/src/main.py
index 06ffdd3..2f2a553 100644
--- a/backend/src/main.py
+++ b/backend/src/main.py
@@ -1,13 +1,17 @@
from typing import List, Optional
+from datetime import datetime
from fastapi import FastAPI, BackgroundTasks, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from .core.config import settings
from .agents.main_agent.agent import MainAgent
from .services.face_detection import FaceDetectionService
+from .services.exam_session_service import ExamSessionService, SessionTransitionError
+from .models.exam_session import ExamSessionConfig, StudentSubmission
app = FastAPI(title=settings.PROJECT_NAME)
face_service = FaceDetectionService()
+exam_session_service = ExamSessionService()
app.add_middleware(
CORSMiddleware,
@@ -33,26 +37,124 @@ class FaceVerifyRequest(BaseModel):
class ResolveAlertRequest(BaseModel):
conflict_id: str
approved: bool = False
- reviewer_id: Optional[str] = None
- reason: Optional[str] = None
-class AdminReviewRequest(BaseModel):
- conflict_id: str
- approved: bool
- reviewer_id: str
- reason: str
-# Initialize the orchestrator and agents
-main_agent = MainAgent()
-gatekeeper_pipeline = main_agent.gatekeeper._pipeline # Access the global pipeline instance
+class ExamSessionCreateRequest(BaseModel):
+ admin_id: str
+ title: str
+ config: Optional[ExamSessionConfig] = None
+
-class GatekeeperVerifyRequest(BaseModel):
+class ExamSessionAssignRequest(BaseModel):
+ submissions: List[StudentSubmission]
+
+
+class ExamSessionRollNumberRequest(BaseModel):
roll_number: str
- face_id: str = None
+
+
+class ExamSessionConfigureRequest(BaseModel):
+ config: ExamSessionConfig
+
+# Initialize the main orchestrator agent
+main_agent = MainAgent()
@app.get("/")
async def root():
- return {"message": "Welcome to ORACLE API", "status": "operational"}
+ return {"message": "Welcome to ORACLE Viva API", "status": "operational"}
+
+
+@app.get("/exam-sessions")
+async def list_exam_sessions():
+ sessions = exam_session_service.list_sessions()
+ return {"items": [session.model_dump(mode="json") for session in sessions]}
+
+
+@app.post("/exam-sessions")
+async def create_exam_session(request: ExamSessionCreateRequest):
+ session = exam_session_service.create_session(request.admin_id, request.title, request.config)
+ return {"session": session.model_dump(mode="json")}
+
+
+@app.get("/exam-sessions/{session_id}")
+async def get_exam_session(session_id: str):
+ session = exam_session_service.get_session(session_id)
+ if session is None:
+ return {"session": None}
+ return {"session": session.model_dump(mode="json")}
+
+
+@app.post("/exam-sessions/{session_id}/configure")
+async def configure_exam_session(session_id: str, request: ExamSessionConfigureRequest):
+ try:
+ session = exam_session_service.configure_session(session_id, request.config)
+ return {"session": session.model_dump(mode="json")}
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
+
+
+@app.post("/exam-sessions/{session_id}/students")
+async def assign_exam_students(session_id: str, request: ExamSessionAssignRequest):
+ session = exam_session_service.assign_students(session_id, request.submissions)
+ return {"session": session.model_dump(mode="json")}
+
+
+@app.post("/exam-sessions/{session_id}/ready")
+async def mark_exam_session_ready(session_id: str):
+ try:
+ session = exam_session_service.set_ready(session_id)
+ return {"session": session.model_dump(mode="json")}
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
+
+
+@app.post("/exam-sessions/{session_id}/activate")
+async def activate_exam_session(session_id: str):
+ try:
+ session = exam_session_service.activate_session(session_id)
+ return {"session": session.model_dump(mode="json")}
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
+
+
+@app.post("/exam-sessions/{session_id}/gatekeeper/precheck")
+async def gatekeeper_precheck(session_id: str, request: ExamSessionRollNumberRequest):
+ try:
+ decision = exam_session_service.gatekeeper_precheck(session_id, request.roll_number)
+ session = exam_session_service.get_session(session_id)
+ return {
+ "decision": decision.model_dump(mode="json"),
+ "session": session.model_dump(mode="json") if session else None,
+ }
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
+
+
+@app.post("/exam-sessions/{session_id}/oracle/start")
+async def start_oracle_analysis(session_id: str, request: ExamSessionRollNumberRequest):
+ try:
+ session = await exam_session_service.start_oracle_analysis(session_id, request.roll_number)
+ return {"session": session.model_dump(mode="json")}
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
+
+
+@app.post("/exam-sessions/{session_id}/complete")
+async def complete_exam_session(session_id: str):
+ try:
+ session = exam_session_service.complete_session(session_id)
+ return {"session": session.model_dump(mode="json")}
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
+
+
+@app.post("/exam-sessions/{session_id}/archive")
+async def archive_exam_session(session_id: str):
+ try:
+ session = exam_session_service.archive_session(session_id)
+ return {"session": session.model_dump(mode="json")}
+ except SessionTransitionError as exc:
+ return {"error": str(exc)}
@app.post("/face/verify")
async def verify_face(request: FaceVerifyRequest):
@@ -74,37 +176,9 @@ async def get_pending_alerts():
@app.post("/face/resolve-alert")
async def resolve_alert(request: ResolveAlertRequest):
- success = face_service.resolve_alert(
- request.conflict_id,
- request.approved,
- request.reviewer_id,
- request.reason
- )
+ success = face_service.resolve_alert(request.conflict_id, request.approved)
return {"success": success}
-@app.get("/face/conflict/{conflict_id}")
-async def get_conflict_details(conflict_id: str):
- details = face_service.get_conflict_details(conflict_id)
- if details is None:
- return {"error": "Conflict not found"}, 404
- return details
-
-@app.post("/admin/review-conflict")
-async def admin_review_conflict(request: AdminReviewRequest):
- success = face_service.admin_review_conflict(
- request.conflict_id,
- request.approved,
- request.reviewer_id,
- request.reason
- )
- if not success:
- return {"error": "Conflict not found"}, 404
- return {"success": True, "message": "Review decision recorded"}
-
-@app.get("/admin/override-log")
-async def get_override_log():
- return face_service.get_override_log()
-
@app.post("/analyze")
async def analyze_repo(request: AnalyzeRequest):
# Legacy REST endpoint for backward compatibility
@@ -116,22 +190,6 @@ async def analyze_repo(request: AnalyzeRequest):
data = context.dict()
return {"status": "success", "data": data}
-@app.post("/gatekeeper/verify")
-async def gatekeeper_verify(request: GatekeeperVerifyRequest):
- """
- Direct endpoint to run the Gatekeeper verification pipeline.
- """
- result = gatekeeper_pipeline.run(request.roll_number, request.face_id)
- return {"status": "success", "data": result.to_dict()}
-
-@app.get("/gatekeeper/registry")
-async def gatekeeper_registry():
- """
- Endpoint to fetch all active registered students.
- """
- students = gatekeeper_pipeline._registry.all_active()
- return {"status": "success", "data": [s.to_dict() for s in students]}
-
@app.websocket("/ws/analyze")
async def websocket_analyze(websocket: WebSocket):
await websocket.accept()
diff --git a/backend/src/models/__init__.py b/backend/src/models/__init__.py
index e69de29..70a4ccb 100644
--- a/backend/src/models/__init__.py
+++ b/backend/src/models/__init__.py
@@ -0,0 +1,39 @@
+from .events import EventType, PlatformEvent
+from .exam_session import (
+ ExamRubric,
+ ExamSession,
+ ExamSessionConfig,
+ ExamSessionState,
+ GatekeeperAdmissionDecision,
+ RubricCriterion,
+ SessionAuditEvent,
+ SessionTimingWindow,
+ StudentSubmission,
+)
+from .intelligence_artifact import (
+ IntelligenceArtifact,
+ IntelligenceCategory,
+ IntelligenceHandoffEvent,
+ VivaTarget,
+ ExecutionNode,
+ ExecutionPath,
+ RuntimeDependency,
+ FailureScenario,
+ ImplementationSignal,
+ WeakPoint,
+ AdaptiveThreshold,
+ VivaSessionState,
+ VoiceSessionConfig,
+)
+from .stage_7_8_9 import (
+ IntegritySignalType,
+ IntegritySeverity,
+ SentinelIntegrityEvent,
+ SentinelAlert,
+ ContradictionChainEntry,
+ EvaluationArtifact,
+ CoreSubject,
+ CurriculumQuestion,
+ CurriculumTransitionState,
+)
+
diff --git a/backend/src/models/events.py b/backend/src/models/events.py
index ddd6e67..4d5fb24 100644
--- a/backend/src/models/events.py
+++ b/backend/src/models/events.py
@@ -5,6 +5,16 @@
import uuid
class EventType(str, Enum):
+ SESSION_CREATED = "session_created"
+ SESSION_CONFIGURED = "session_configured"
+ SESSION_READY = "session_ready"
+ SESSION_LIVE = "session_live"
+ SESSION_ACTIVE_VIVA = "session_active_viva"
+ SESSION_ARCHIVED = "session_archived"
+ STUDENT_ADMITTED = "student_admitted"
+ STUDENT_REJECTED = "student_rejected"
+ ORACLE_ANALYSIS_STARTED = "oracle_analysis_started"
+ ORACLE_ANALYSIS_COMPLETED = "oracle_analysis_completed"
SESSION_STARTED = "session_started"
SESSION_COMPLETED = "session_completed"
IDENTITY_VERIFIED = "identity_verified"
@@ -49,6 +59,46 @@ class EventType(str, Enum):
FAILURE_PATH_DETECTED = "failure_path.detected"
DEAD_PATH_DETECTED = "dead_path.detected"
EXCEPTION_FLOW_ANALYZED = "exception_flow.analyzed"
+
+ # Stage 4: ORACLE Intelligence Handoff
+ ORACLE_INTELLIGENCE_READY = "oracle_intelligence_ready"
+
+ # Stage 5: MAIN Agent Viva Events
+ VIVA_SESSION_STARTED = "viva_session_started"
+ VIVA_QUESTION_ASKED = "viva_question_asked"
+ VIVA_RESPONSE_RECEIVED = "viva_response_received"
+ VIVA_EVALUATION_COMPLETE = "viva_evaluation_complete"
+ VIVA_FOLLOW_UP_GENERATED = "viva_follow_up_generated"
+ VIVA_CONTRADICTION_DETECTED = "viva_contradiction_detected"
+ VIVA_TOPIC_ESCALATED = "viva_topic_escalated"
+ VIVA_SESSION_COMPLETED = "viva_session_completed"
+
+ # Stage 6: Voice Infrastructure Events
+ VOICE_SESSION_STARTED = "voice_session_started"
+ VOICE_QUESTION_PLAYED = "voice_question_played"
+ VOICE_LISTENING_STARTED = "voice_listening_started"
+ VOICE_LISTENING_STOPPED = "voice_listening_stopped"
+ VOICE_TRANSCRIPTION_RECEIVED = "voice_transcription_received"
+ VOICE_TRANSCRIPTION_NORMALIZED = "voice_transcription_normalized"
+ VOICE_SESSION_ENDED = "voice_session_ended"
+
+ # Stage 7: SENTINEL Parallel Oversight Events
+ INTEGRITY_ALERT_GENERATED = "integrity_alert_generated"
+ PROLONGED_OFFSCREEN_FOCUS = "prolonged_offscreen_focus"
+ REPEATED_GAZE_SHIFT = "repeated_gaze_shift"
+ SESSION_INTERRUPTION = "session_interruption"
+ SUSPICIOUS_AUDIO_PATTERN = "suspicious_audio_pattern"
+ LOW_VISIBILITY_WARNING = "low_visibility_warning"
+ MANUAL_REVIEW_RECOMMENDED = "manual_review_recommended"
+
+ # Stage 8: MAIN Agent Evaluation Loop Events
+ IMPLEMENTATION_FAMILIARITY_UPDATED = "implementation_familiarity_updated"
+ CONTRADICTION_CHAIN_UPDATED = "contradiction_chain_updated"
+ FOLLOW_UP_ESCALATION = "follow_up_escalation"
+
+ # Stage 9: Curriculum Progression Events
+ CURRICULUM_TRANSITION_STARTED = "curriculum_transition_started"
+ CURRICULUM_TOPIC_COMPLETED = "curriculum_topic_completed"
class PlatformEvent(BaseModel):
event_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
diff --git a/backend/src/models/exam_session.py b/backend/src/models/exam_session.py
new file mode 100644
index 0000000..7f6e3ff
--- /dev/null
+++ b/backend/src/models/exam_session.py
@@ -0,0 +1,107 @@
+from __future__ import annotations
+
+from datetime import datetime
+from enum import Enum
+from typing import Any, Dict, List, Optional
+import uuid
+
+from pydantic import BaseModel, Field
+
+
+class ExamSessionState(str, Enum):
+ DRAFT = "DRAFT"
+ CONFIGURED = "CONFIGURED"
+ READY = "READY"
+ LIVE = "LIVE"
+ ACTIVE_VIVA = "ACTIVE_VIVA"
+ COMPLETED = "COMPLETED"
+ ARCHIVED = "ARCHIVED"
+
+
+class SessionTimingWindow(BaseModel):
+ opens_at: Optional[datetime] = None
+ closes_at: Optional[datetime] = None
+ viva_duration_minutes: int = Field(default=15, ge=1, le=240)
+ check_in_grace_minutes: int = Field(default=5, ge=0, le=60)
+
+
+class RubricCriterion(BaseModel):
+ name: str
+ description: Optional[str] = None
+ max_score: int = Field(default=10, ge=1, le=100)
+
+
+class ExamRubric(BaseModel):
+ title: str = "Default Viva Rubric"
+ criteria: List[RubricCriterion] = Field(default_factory=list)
+
+
+class StudentSubmission(BaseModel):
+ roll_number: str
+ repository_url: Optional[str] = None
+ document_paths: List[str] = Field(default_factory=list)
+ batch_label: Optional[str] = None
+ assignment_state: str = "assigned"
+
+
+class ExamSessionConfig(BaseModel):
+ subject: str
+ course: str
+ semester: str
+ subject_code: Optional[str] = None
+ academic_year: Optional[str] = None
+ department: Optional[str] = None
+ instructor_name: Optional[str] = None
+ exam_coordinator: Optional[str] = None
+ timing_window: SessionTimingWindow = Field(default_factory=SessionTimingWindow)
+ rubric: ExamRubric = Field(default_factory=ExamRubric)
+ notes: Optional[str] = None
+
+
+class SessionAuditEvent(BaseModel):
+ event_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
+ session_id: str
+ event_type: str
+ actor: str
+ payload: Dict[str, Any] = Field(default_factory=dict)
+
+
+class GatekeeperAdmissionDecision(BaseModel):
+ decision_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
+ session_id: str
+ student_roll_number: str
+ admitted: bool
+ reason: Optional[str] = None
+ session_state: ExamSessionState
+ timing_valid: bool = False
+ submission_present: bool = False
+ duplicate_join: bool = False
+ suspicious: bool = False
+ metadata: Dict[str, Any] = Field(default_factory=dict)
+
+
+class ExamSession(BaseModel):
+ session_id: str
+ admin_id: str
+ title: str = "Untitled Viva Session"
+ state: ExamSessionState = ExamSessionState.DRAFT
+ created_at: datetime = Field(default_factory=datetime.utcnow)
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
+ activated_at: Optional[datetime] = None
+ completed_at: Optional[datetime] = None
+ archived_at: Optional[datetime] = None
+ config: Optional[ExamSessionConfig] = None
+ assigned_students: List[StudentSubmission] = Field(default_factory=list)
+ gatekeeper_decisions: List[GatekeeperAdmissionDecision] = Field(default_factory=list)
+ analysis_artifacts: List[Dict[str, Any]] = Field(default_factory=list)
+ audit_events: List[SessionAuditEvent] = Field(default_factory=list)
+ admitted_roll_numbers: List[str] = Field(default_factory=list)
+ active_student_roll_number: Optional[str] = None
+ oracle_started_at: Optional[datetime] = None
+ oracle_completed_at: Optional[datetime] = None
+ oracle_status: str = "not_started"
+
+ def touch(self) -> None:
+ self.updated_at = datetime.utcnow()
diff --git a/backend/src/models/intelligence_artifact.py b/backend/src/models/intelligence_artifact.py
new file mode 100644
index 0000000..e3ddfea
--- /dev/null
+++ b/backend/src/models/intelligence_artifact.py
@@ -0,0 +1,196 @@
+"""
+ORACLE Intelligence Artifacts — Stage 4 Handoff Model
+
+Structured, deterministic intelligence handoff from ORACLE analysis to MAIN Agent.
+All artifacts are evidence-grounded, explainable, and audit-safe.
+"""
+
+from typing import List, Dict, Any, Optional
+from pydantic import BaseModel, Field
+from enum import Enum
+from datetime import datetime
+
+
+class IntelligenceCategory(str, Enum):
+ """Categorizes the type of intelligence artifact."""
+ ARCHITECTURE = "ARCHITECTURE"
+ RUNTIME_FLOW = "RUNTIME_FLOW"
+ SECURITY = "SECURITY"
+ SCALABILITY = "SCALABILITY"
+ FAILURE_PATH = "FAILURE_PATH"
+ OBSERVABLE_SIGNAL = "OBSERVABLE_SIGNAL"
+ IMPLEMENTATION_RISK = "IMPLEMENTATION_RISK"
+ WEAK_POINT = "WEAK_POINT"
+
+
+class RuntimeDependency(BaseModel):
+ """Tracks runtime dependencies critical to understanding implementation."""
+ name: str
+ type: str # "LIBRARY", "SERVICE", "MIDDLEWARE", "DATABASE", "CACHE"
+ usage_pattern: str
+ criticality: str # "LOW", "MEDIUM", "HIGH", "CRITICAL"
+ evidence_file: Optional[str] = None
+ evidence_snippet: Optional[str] = None
+
+
+class FailureScenario(BaseModel):
+ """Describes a specific failure scenario and its propagation."""
+ scenario_name: str
+ trigger: str # What causes this failure
+ propagation_path: List[str] # How failure propagates through system
+ impact: str # What breaks as a result
+ severity: str # "LOW", "MEDIUM", "HIGH", "CRITICAL"
+ detectability: str # "EASY", "MODERATE", "HARD"
+ evidence_file: Optional[str] = None
+ related_nodes: List[str] = Field(default_factory=list)
+
+
+class ExecutionNode(BaseModel):
+ """A single node in the execution graph."""
+ node_id: str
+ label: str
+ node_type: str # "REQUEST_HANDLER", "MIDDLEWARE", "DB_QUERY", "SERVICE_CALL", "CACHE", "AUTH", "ERROR_HANDLER"
+ implementation_details: str # Brief description of what happens here
+ dependencies: List[str] = Field(default_factory=list) # Other nodes this depends on
+ failure_modes: List[str] = Field(default_factory=list) # How this can fail
+
+
+class ExecutionPath(BaseModel):
+ """A traced execution path through the system."""
+ path_id: str
+ description: str
+ nodes: List[str] # Order of ExecutionNode IDs
+ scenario: str # "HAPPY_PATH", "ERROR_PATH", "EDGE_CASE"
+ criticality: str # "LOW", "MEDIUM", "HIGH"
+ evidence_file: Optional[str] = None
+
+
+class ImplementationSignal(BaseModel):
+ """Observable evidence of implementation decisions."""
+ signal_type: str # "DESIGN_PATTERN", "ERROR_HANDLING", "CACHING_STRATEGY", "ASYNC_HANDLING", "STATE_MANAGEMENT"
+ description: str
+ evidence: str # Actual code or evidence
+ confidence: float # 0.0 - 1.0
+ risk_level: str # "LOW", "MEDIUM", "HIGH"
+ related_risk: Optional[str] = None
+
+
+class WeakPoint(BaseModel):
+ """Areas where implementation is fragile or shows poor understanding."""
+ area: str # What part of the system
+ weakness: str # Specific weakness
+ why_problematic: str # Why this is concerning for a viva
+ testing_approach: str # How to probe this in viva
+ evidence_file: Optional[str] = None
+
+
+class VivaTarget(BaseModel):
+ """Focused viva target with grounding and evidence."""
+ target_id: str
+ question: str
+ category: IntelligenceCategory
+ difficulty: str # "FOUNDATIONAL", "MEDIUM", "HARD"
+ depth_score: float # 0-10, how deep understanding is required
+ why_important: str # Why ask this question
+ evidence_references: List[str] = Field(default_factory=list) # Files/lines this relates to
+ follow_up_paths: List[str] = Field(default_factory=list) # Possible follow-ups if answer is shallow
+ expected_coverage: List[str] = Field(default_factory=list) # What student should cover
+ red_flags: List[str] = Field(default_factory=list) # Concerning responses to watch for
+
+
+class AdaptiveThreshold(BaseModel):
+ """Thresholds for adapting viva difficulty."""
+ topic: str
+ weak_point_triggers: List[str] = Field(default_factory=list) # Triggers for escalation
+ strong_point_indicators: List[str] = Field(default_factory=list) # Triggers for advancement
+ contradiction_escalation: bool = True # Escalate on contradictions
+
+
+class IntelligenceArtifact(BaseModel):
+ """
+ Complete intelligence handoff from ORACLE to MAIN Agent.
+ Stage 4 output: structured, deterministic, evidence-grounded.
+ """
+ # Metadata
+ artifact_id: str = Field(default_factory=lambda: f"artifact_{datetime.utcnow().isoformat()}")
+ session_id: str
+ oracle_version: str = "v1"
+ generated_at: datetime = Field(default_factory=datetime.utcnow)
+ analysis_duration_seconds: float
+
+ # Project context
+ project_name: str
+ project_type: str
+ backend_stack: Dict[str, str] # framework, db, cache, etc.
+ frontend_stack: Optional[Dict[str, str]] = None
+ architecture_pattern: str # e.g., "MVC", "Microservices", "Monolith"
+
+ # Core execution intelligence
+ execution_graph_nodes: List[ExecutionNode] = Field(default_factory=list)
+ execution_paths: List[ExecutionPath] = Field(default_factory=list)
+ runtime_dependencies: List[RuntimeDependency] = Field(default_factory=list)
+
+ # Failure and risk intelligence
+ failure_scenarios: List[FailureScenario] = Field(default_factory=list)
+ implementation_risks: List[Dict[str, Any]] = Field(default_factory=list)
+ weak_points: List[WeakPoint] = Field(default_factory=list)
+
+ # Viva intelligence
+ viva_targets: List[VivaTarget] = Field(default_factory=list)
+ adaptive_thresholds: List[AdaptiveThreshold] = Field(default_factory=list)
+
+ # Implementation signals (observable evidence)
+ implementation_signals: List[ImplementationSignal] = Field(default_factory=list)
+
+ # Explainability
+ summary: str # Human-readable summary of analysis
+ key_findings: List[str] = Field(default_factory=list)
+ analysis_confidence: float # Overall confidence 0.0-1.0
+ limitations: List[str] = Field(default_factory=list) # What wasn't analyzed
+
+ # Session binding
+ serialization_version: str = "1.0"
+ deterministic_hash: Optional[str] = None # For replay verification
+
+
+class IntelligenceHandoffEvent(BaseModel):
+ """Event emitted when ORACLE completes and hands off to MAIN."""
+ event_type: str = "ORACLE_INTELLIGENCE_READY"
+ session_id: str
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
+ artifact_id: str
+ artifact_summary: Dict[str, Any] # Quick stats: num_targets, num_risks, etc.
+ next_action: str = "MAIN_AGENT_START_VIVA" # Always this at end of Stage 4
+
+
+class VivaSessionState(BaseModel):
+ """Tracks state of a viva session in Stage 5."""
+ session_id: str
+ viva_phase: str # "STARTED", "INTRODUCTORY", "CORE", "DEEP_DIVE", "CONTRADICTION_PROBE", "CLOSING"
+ current_topic: Optional[str] = None
+ current_target_id: Optional[str] = None
+ questions_asked: int = 0
+ contradictions_found: int = 0
+ weak_areas_detected: List[str] = Field(default_factory=list)
+ strong_areas_detected: List[str] = Field(default_factory=list)
+ adaptive_difficulty: float = 5.0 # 0-10, increases/decreases based on performance
+
+ # Transcript references
+ transcript_segment_ids: List[str] = Field(default_factory=list)
+ last_question_id: Optional[str] = None
+ last_response_text: Optional[str] = None
+ evaluation_score: Optional[float] = None
+
+
+class VoiceSessionConfig(BaseModel):
+ """Configuration for voice viva in Stage 6."""
+ enabled: bool = True
+ tts_provider: str = "system" # "system", "deepgram", "google"
+ stt_provider: str = "deepgram" # "deepgram", "google", "azure"
+ voice_language: str = "en-US"
+ speech_rate: float = 1.0
+ silence_timeout_ms: int = 3000
+ max_response_duration_seconds: int = 120
+ enable_transcript_normalization: bool = True
+ save_audio_recordings: bool = True
+ audio_storage_path: Optional[str] = None
diff --git a/backend/src/models/stage_7_8_9.py b/backend/src/models/stage_7_8_9.py
new file mode 100644
index 0000000..b10f76b
--- /dev/null
+++ b/backend/src/models/stage_7_8_9.py
@@ -0,0 +1,109 @@
+"""
+Stage 7-9 Runtime Models
+
+Deterministic, evidence-grounded models for:
+- Stage 7 SENTINEL integrity oversight
+- Stage 8 MAIN evaluation loop artifacts
+- Stage 9 curriculum-linked questioning
+"""
+
+from datetime import datetime
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel, Field
+
+
+class IntegritySignalType(str, Enum):
+ PROLONGED_OFFSCREEN_FOCUS = "prolonged_offscreen_focus"
+ REPEATED_GAZE_SHIFT = "repeated_gaze_shift"
+ SESSION_INTERRUPTION = "session_interruption"
+ SUSPICIOUS_AUDIO_PATTERN = "suspicious_audio_pattern"
+ LOW_VISIBILITY_WARNING = "low_visibility_warning"
+ CONTRADICTION_ESCALATION = "contradiction_escalation"
+ CONFIDENCE_INSTABILITY = "confidence_instability"
+ EXCESSIVE_SILENCE_PATTERN = "excessive_silence_pattern"
+ ENVIRONMENT_CHANGE = "environment_change"
+
+
+class IntegritySeverity(str, Enum):
+ LOW = "LOW"
+ MEDIUM = "MEDIUM"
+ HIGH = "HIGH"
+
+
+class SentinelIntegrityEvent(BaseModel):
+ event_id: str
+ session_id: str
+ signal_type: IntegritySignalType
+ severity: IntegritySeverity
+ observed_at: datetime = Field(default_factory=datetime.utcnow)
+ evidence: Dict[str, Any] = Field(default_factory=dict)
+ explanation: str
+ replay_metadata: Dict[str, Any] = Field(default_factory=dict)
+
+
+class SentinelAlert(BaseModel):
+ alert_id: str
+ session_id: str
+ created_at: datetime = Field(default_factory=datetime.utcnow)
+ event_ids: List[str] = Field(default_factory=list)
+ manual_review_recommended: bool = False
+ reason: str
+
+
+class ContradictionChainEntry(BaseModel):
+ chain_id: str
+ target_id: str
+ previous_claim: str
+ current_claim: str
+ severity: str
+ turn_index: int
+ detected_at: datetime = Field(default_factory=datetime.utcnow)
+
+
+class EvaluationArtifact(BaseModel):
+ session_id: str
+ turn_index: int
+ target_id: str
+ implementation_specificity: float
+ runtime_understanding: float
+ operational_reasoning: float
+ architectural_understanding: float
+ failure_path_awareness: float
+ tradeoff_understanding: float
+ consistency_score: float
+ implementation_familiarity: float
+ topic_coverage: Dict[str, float] = Field(default_factory=dict)
+ weak_areas: List[str] = Field(default_factory=list)
+ follow_up_chain: List[str] = Field(default_factory=list)
+ contradiction_chain: List[ContradictionChainEntry] = Field(default_factory=list)
+ created_at: datetime = Field(default_factory=datetime.utcnow)
+
+
+class CoreSubject(str, Enum):
+ DSA = "DSA"
+ DBMS = "DBMS"
+ OPERATING_SYSTEMS = "OPERATING_SYSTEMS"
+ COMPUTER_NETWORKS = "COMPUTER_NETWORKS"
+ OOP = "OOP"
+ SOFTWARE_ENGINEERING = "SOFTWARE_ENGINEERING"
+ SYSTEM_DESIGN = "SYSTEM_DESIGN"
+ CLOUD_DEVOPS = "CLOUD_DEVOPS"
+
+
+class CurriculumQuestion(BaseModel):
+ question_id: str
+ subject: CoreSubject
+ prompt: str
+ linked_implementation_signal: str
+ difficulty: str
+ expected_coverage: List[str] = Field(default_factory=list)
+
+
+class CurriculumTransitionState(BaseModel):
+ session_id: str
+ transition_started_at: datetime = Field(default_factory=datetime.utcnow)
+ started: bool = False
+ completed_subjects: List[CoreSubject] = Field(default_factory=list)
+ asked_questions: List[str] = Field(default_factory=list)
diff --git a/frontend/app.js b/frontend/app.js
deleted file mode 100644
index bbbfbd4..0000000
--- a/frontend/app.js
+++ /dev/null
@@ -1,481 +0,0 @@
-'use strict';
-// ═══════════════════════════════════════════
-// El — AI Examiner | app.js
-// ✅ Always-on voice input (SpeechRecognition)
-// ✅ Mic auto-starts on boot, pauses when El speaks
-// ✅ Transcribed text auto-sends after speech ends
-// ✅ Camera + mic media handled in browser
-// ✅ AI calls go to /api/chat (backend — no key here)
-// ═══════════════════════════════════════════
-
-const BACKEND_URL = 'http://localhost:3333/api/chat';
-
-// ── DOM ──────────────────────────────────────────────────────────────
-const gate = document.getElementById('gate');
-const gateBtn = document.getElementById('gate-btn');
-const gateLoading = document.getElementById('gate-loading');
-const gateErr = document.getElementById('gate-err');
-
-const app = document.getElementById('app');
-const camVideo = document.getElementById('cam-video');
-const camAvatar = document.getElementById('cam-avatar');
-const camSpeakRing = document.getElementById('cam-speak-ring');
-const mmBars = document.querySelectorAll('.mm-bars i');
-const mmMuted = document.getElementById('mm-muted');
-const speakerBtn = document.getElementById('speaker-btn');
-const spkOn = document.getElementById('spk-on');
-const spkOff = document.getElementById('spk-off');
-
-const elsOrb = document.getElementById('els-orb');
-const elsState = document.getElementById('els-state');
-const elsWaves = document.getElementById('els-waves');
-
-const heroOrb = document.getElementById('hero-orb');
-const heroText = document.getElementById('hero-text');
-const heroSub = document.getElementById('hero-sub');
-const quickChips = document.getElementById('quick-chips');
-const hero = document.getElementById('hero');
-const msgs = document.getElementById('msgs');
-const chatScroll = document.getElementById('chat-scroll');
-
-const chatInput = document.getElementById('chat-input');
-const plusBtn = document.getElementById('plus-btn');
-const fileInput = document.getElementById('file-input');
-const fileChips = document.getElementById('file-chips');
-const sendBtn = document.getElementById('send-btn');
-const deniedOverlay = document.getElementById('denied-overlay');
-const listenRing = document.getElementById('listen-ring');
-const listenLabel = document.getElementById('listen-label');
-
-// ── STATE ────────────────────────────────────────────────────────────
-let stream = null;
-let audioCtx = null;
-let analyser = null;
-let vizRAF = null;
-let voiceEnabled = true;
-let isSpeaking = false;
-let pendingFiles = [];
-let conversation = [];
-
-// Speech Recognition
-const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
-let recognition = null;
-let isListening = false;
-let autoSendTimer = null;
-let srEnabled = !!SR; // false if browser doesn't support it
-
-// ── UTILS ────────────────────────────────────────────────────────────
-const show = el => el && el.classList.remove('hidden');
-const hide = el => el && el.classList.add('hidden');
-const sleep = ms => new Promise(r => setTimeout(r, ms));
-const now = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
-
-// ── GATE ─────────────────────────────────────────────────────────────
-async function requestPermissionsAndBoot(launchExam = false) {
- hide(gateErr);
- document.getElementById('gate-btn').style.display = 'none';
- show(gateLoading);
-
- try {
- stream = await navigator.mediaDevices.getUserMedia({
- video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' },
- audio: { echoCancellation: true, noiseSuppression: true }
- });
-
- gate.classList.add('out');
- await sleep(450);
- gate.style.display = 'none';
- show(app);
- bootApp(launchExam);
- } catch (err) {
- document.getElementById('gate-btn').style.display = '';
- hide(gateLoading);
- handlePermErr(err);
- }
-}
-
-gateBtn.addEventListener('click', () => requestPermissionsAndBoot(true));
-
-// ── BOOT ─────────────────────────────────────────────────────────────
-function bootApp(launchExam = false) {
- camVideo.srcObject = stream;
- camVideo.play().catch(() => {});
- if (stream.getAudioTracks().length > 0) startAnalyser();
- startSession(launchExam);
-}
-
-// ── SESSION START ────────────────────────────────────────────────────
-function startSession(launchExam = false) {
- if (heroText) heroText.style.display = 'none';
-
- if (launchExam) {
- const examMsg = "Let's start the exam. Please begin!";
- addElMsg(examMsg, true);
- elSpeak(examMsg);
- conversation.push({ role: 'model', parts: [{ text: examMsg }] });
- chatInput.value = "Let's start the exam.";
- sendMessage();
- } else {
- const h = new Date().getHours();
- const greet = h < 12 ? 'Good morning' : h < 17 ? 'Good afternoon' : 'Good evening';
- const opening = `${greet}! I'm El, your AI Examiner. How can I help you today?`;
- addElMsg(opening, true);
- elSpeak(opening);
- conversation.push({ role: 'model', parts: [{ text: opening }] });
- }
- setTimeout(startListening, 800);
-}
-
-// ══════════════════════════════════════════════
-// SPEECH RECOGNITION — always-on voice input
-// ══════════════════════════════════════════════
-function initSR() {
- if (!srEnabled) return;
-
- recognition = new SR();
- recognition.lang = 'en-US';
- recognition.continuous = false; // restart loop gives more reliability
- recognition.interimResults = true;
- recognition.maxAlternatives = 1;
-
- recognition.onstart = () => {
- isListening = true;
- setListenUI(true);
- };
-
- recognition.onresult = (e) => {
- let interim = '';
- let final = '';
- for (let i = e.resultIndex; i < e.results.length; i++) {
- if (e.results[i].isFinal) final += e.results[i][0].transcript;
- else interim += e.results[i][0].transcript;
- }
-
- const spoken = (final || interim).trim();
- if (spoken) {
- chatInput.value = spoken;
- autoResize();
- }
-
- if (final.trim()) {
- // Small pause to let user finish sentence, then auto-send
- clearTimeout(autoSendTimer);
- autoSendTimer = setTimeout(() => {
- if (chatInput.value.trim()) sendMessage();
- }, 900);
- }
- };
-
- recognition.onend = () => {
- isListening = false;
- setListenUI(false);
- // Restart loop unless El is speaking
- if (!isSpeaking && srEnabled) {
- setTimeout(startListening, 250);
- }
- };
-
- recognition.onerror = (e) => {
- isListening = false;
- setListenUI(false);
- // 'aborted' and 'no-speech' are normal — just restart
- if (e.error !== 'aborted' && e.error !== 'no-speech') {
- console.warn('[SR]', e.error);
- }
- if (!isSpeaking && srEnabled) {
- setTimeout(startListening, 400);
- }
- };
-}
-
-function startListening() {
- if (!srEnabled || isSpeaking || isListening) return;
- try {
- if (!recognition) initSR();
- recognition.start();
- } catch (_) {
- // Already started — ignore
- }
-}
-
-function stopListening() {
- if (recognition && isListening) {
- try { recognition.stop(); } catch (_) {}
- isListening = false;
- setListenUI(false);
- }
-}
-
-function setListenUI(active) {
- if (listenRing) listenRing.classList.toggle('active', active);
- const listenDot = document.getElementById('listen-dot');
- if (listenDot) listenDot.classList.toggle('on', active);
- if (listenLabel) listenLabel.textContent = active ? '🎙️ Listening — speak now…' : 'Speak your answer';
- chatInput.placeholder = active
- ? '🎙️ Listening — speak now…'
- : 'Write your answer…';
-}
-
-// ── MIC ANALYSER ─────────────────────────────────────────────────────
-function startAnalyser() {
- try {
- audioCtx = new (window.AudioContext || window.webkitAudioContext)();
- const src = audioCtx.createMediaStreamSource(stream);
- analyser = audioCtx.createAnalyser();
- analyser.fftSize = 64;
- analyser.smoothingTimeConstant = 0.75;
- src.connect(analyser);
- const data = new Uint8Array(analyser.frequencyBinCount);
- function draw() {
- vizRAF = requestAnimationFrame(draw);
- analyser.getByteFrequencyData(data);
- const avg = data.reduce((a, b) => a + b, 0) / data.length / 255;
- mmBars.forEach((bar, i) => {
- const idx = Math.floor((i / mmBars.length) * data.length);
- bar.style.height = Math.max(4, (data[idx] / 255) * 24) + 'px';
- bar.style.opacity = 0.3 + (data[idx] / 255) * 0.7;
- });
- camSpeakRing.classList.toggle('active', avg > 0.05);
- }
- draw();
- } catch (e) { console.warn('AudioCtx', e); }
-}
-
-// ── EL SPEAKS ────────────────────────────────────────────────────────
-function elSpeak(text) {
- if (!voiceEnabled) {
- // No TTS — start listening immediately
- setTimeout(startListening, 300);
- return;
- }
- window.speechSynthesis.cancel();
-
- const utter = new SpeechSynthesisUtterance(text);
- utter.rate = 0.95;
- utter.pitch = 1.0;
- utter.volume = 1.0;
-
- const voices = window.speechSynthesis.getVoices();
- const prefs = ['Google UK English Female','Microsoft Zira Desktop','Samantha','Karen','Moira'];
- for (const name of prefs) {
- const v = voices.find(v => v.name === name);
- if (v) { utter.voice = v; break; }
- }
- if (!utter.voice) utter.voice = voices.find(v => v.lang.startsWith('en')) || null;
-
- utter.onstart = () => {
- isSpeaking = true;
- stopListening(); // pause mic while El talks
- heroOrb.classList.add('speaking');
- elsOrb.classList.add('pulse');
- elsState.textContent = 'Speaking…';
- show(elsWaves);
- };
-
- utter.onend = utter.onerror = () => {
- isSpeaking = false;
- heroOrb.classList.remove('speaking');
- elsOrb.classList.remove('pulse');
- elsState.textContent = 'Listening for you…';
- hide(elsWaves);
- // Auto-start mic after El finishes
- setTimeout(startListening, 400);
- };
-
- window.speechSynthesis.speak(utter);
-}
-
-if (speechSynthesis.onvoiceschanged !== undefined)
- speechSynthesis.onvoiceschanged = () => speechSynthesis.getVoices();
-
-speakerBtn.addEventListener('click', () => {
- voiceEnabled = !voiceEnabled;
- if (!voiceEnabled) window.speechSynthesis.cancel();
- voiceEnabled ? (show(spkOn), hide(spkOff)) : (hide(spkOn), show(spkOff));
-});
-
-// ── TOGGLE CAMERA — removed, camera is always-on ────────────────────
-
-// ── FILE UPLOAD ──────────────────────────────────────────────────────
-plusBtn.addEventListener('click', () => fileInput.click());
-fileInput.addEventListener('change', () => {
- Array.from(fileInput.files).forEach(f => {
- pendingFiles.push(f);
- const chip = document.createElement('div');
- chip.className = 'fc';
- chip.innerHTML = `📎 ${trunc(f.name, 10)} `;
- chip.querySelector('button').addEventListener('click', () => {
- pendingFiles = pendingFiles.filter(x => x.name !== f.name);
- chip.remove();
- });
- fileChips.appendChild(chip);
- });
- fileInput.value = '';
-});
-const trunc = (s, n) => s.length <= n ? s : s.slice(0, n) + '…';
-
-// ── SEND MESSAGE ─────────────────────────────────────────────────────
-async function sendMessage() {
- const text = chatInput.value.trim();
- if (!text && pendingFiles.length === 0) return;
-
- clearTimeout(autoSendTimer);
- stopListening(); // pause while processing
-
- if (isSpeaking) window.speechSynthesis.cancel();
-
- // Collapse hero on first message
- if (hero && !hero.classList.contains('gone')) {
- hero.style.display = 'none';
- hero.classList.add('gone');
- }
-
- // User bubble
- const userEl = document.createElement('div');
- userEl.className = 'msg-user';
- if (text) userEl.textContent = text;
- pendingFiles.forEach(f => {
- const t = document.createElement('div');
- t.className = 'file-tag';
- t.textContent = `📎 ${f.name}`;
- userEl.appendChild(t);
- });
- msgs.appendChild(userEl);
- scrollBot();
-
- const userText = text || `[User uploaded: ${pendingFiles.map(f => f.name).join(', ')}]`;
- pendingFiles = [];
- fileChips.innerHTML = '';
- chatInput.value = '';
- autoResize();
-
- conversation.push({ role: 'user', parts: [{ text: userText }] });
-
- sendBtn.disabled = true;
- elsState.textContent = 'Thinking…';
- const typingWrap = showTyping();
-
- let reply = '';
- try {
- reply = await callBackend(conversation);
- } catch (e) {
- reply = "I had a little trouble connecting. Could you try again?";
- console.error('[chat]', e.message);
- }
-
- typingWrap.remove();
- sendBtn.disabled = false;
-
- addElMsg(reply, true);
- elSpeak(reply); // mic auto-restarts after El finishes
- conversation.push({ role: 'model', parts: [{ text: reply }] });
-}
-
-sendBtn.addEventListener('click', sendMessage);
-chatInput.addEventListener('keydown', e => {
- if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
-});
-
-// ── CALL BACKEND ─────────────────────────────────────────────────────
-async function callBackend(messages) {
- const res = await fetch(BACKEND_URL, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ messages })
- });
- const data = await res.json();
- if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
- return data.reply;
-}
-
-// ── ADD EL BUBBLE ────────────────────────────────────────────────────
-function addElMsg(text, animate = false) {
- const wrap = document.createElement('div');
- wrap.className = 'msg-el';
- const av = document.createElement('div');
- av.className = 'el-av';
- const right = document.createElement('div');
- const bbl = document.createElement('div');
- bbl.className = 'el-bbl';
- const timeEl = document.createElement('div');
- timeEl.className = 'el-time';
- timeEl.innerHTML = `${now()} `;
- right.append(bbl, timeEl);
- wrap.append(av, right);
- msgs.appendChild(wrap);
- scrollBot();
-
- if (!animate) { bbl.textContent = text; return; }
- let i = 0;
- const iv = setInterval(() => {
- bbl.textContent = text.slice(0, i++);
- if (i > text.length) clearInterval(iv);
- scrollBot();
- }, 16);
-}
-
-// ── TYPING INDICATOR ─────────────────────────────────────────────────
-function showTyping() {
- const wrap = document.createElement('div');
- wrap.className = 'msg-el';
- const av = document.createElement('div');
- av.className = 'el-av';
- const bbl = document.createElement('div');
- bbl.className = 'el-bbl';
- bbl.innerHTML = '
';
- wrap.append(av, bbl);
- msgs.appendChild(wrap);
- scrollBot();
- return wrap;
-}
-
-function useChip(btn) {
- chatInput.value = btn.textContent.replace(/^[^\w]+/, '').trim();
- chatInput.focus();
-}
-
-function autoResize() {
- chatInput.style.height = 'auto';
- chatInput.style.height = Math.min(chatInput.scrollHeight, 130) + 'px';
-}
-chatInput.addEventListener('input', autoResize);
-function scrollBot() { chatScroll.scrollTop = chatScroll.scrollHeight; }
-
-// ── PERMISSION ERROR ─────────────────────────────────────────────────
-function handlePermErr(err) {
- if (['NotAllowedError', 'PermissionDeniedError'].includes(err.name)) {
- show(deniedOverlay); return;
- }
- gateErr.textContent = `Could not access media: ${err.message}`;
- show(gateErr);
-}
-
-// ── BROWSER CHECK ────────────────────────────────────────────────────
-if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
- gateBtn.disabled = true;
- gateErr.textContent = 'Use Chrome, Firefox, Edge, or Safari over localhost / HTTPS.';
- show(gateErr);
-}
-
-if (!SR) {
- console.warn('SpeechRecognition not supported in this browser. Use Chrome for voice input.');
-}
-
-// ── CLEANUP ──────────────────────────────────────────────────────────
-window.addEventListener('beforeunload', () => {
- window.speechSynthesis.cancel();
- stopListening();
- if (vizRAF) cancelAnimationFrame(vizRAF);
- if (audioCtx) audioCtx.close();
- if (stream) stream.getTracks().forEach(t => t.stop());
-});
-
-// ── REMOVE SPLINE LOGO ───────────────────────────────────────────────
-setInterval(() => {
- document.querySelectorAll('spline-viewer').forEach(viewer => {
- if (viewer.shadowRoot) {
- const logo = viewer.shadowRoot.querySelector('#logo');
- if (logo) logo.remove();
- }
- });
-}, 1000);
diff --git a/frontend/index.html b/frontend/index.html
index c3fa418..a478382 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,172 +1,194 @@
-
- El — AI Examiner
+ ORACLE Viva Operations
-
+
-
-
-
+
+
+
+
University Viva Infrastructure
+
ORACLE Viva Operations
+
Session-centered administration for pre-viva preparation, gatekeeper validation, and ORACLE analysis start.
+
+
+ Backend
+ Checking…
+ Connected to the exam-session API when available.
+
+
-
-
-
-
-
-
-
Hi, I'm El 👋
-
Your personal AI Examiner.
I just need your camera & microphone to get started.
-
-
-
-
-
- Requesting access…
-
-
-
+
-
-
+
+
+
+
+
Stage 1
+
Pre-viva preparation
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
Stage 2
+
Gatekeeper pre-check
+
-
- You
-
+
+
+
+
+ State
+ —
+
+
+ Students
+ 0
+
+
+ Admissions
+ 0
+
+
+
+
-
-
-
-
-
El
-
Ready
+
+
+
-
-
+
+
+
Latest admission decision
+
No admission check yet.
-
-
-
-
-
-
-