Zayne Rea Sprague Claude Opus 4.6 commited on
Commit ·
e6cfd0f
1
Parent(s): 20b1a79
feat: add AdaEvolve visualizer tab for reasoning stickiness analysis
Browse filesNew tab with timeline view (L1/L2/L3 color-coded bars), detail view
with reasoning traces, code, and meta-guidance tactics. Backend API
for loading AdaEvolve HF datasets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- backend/api/adaevolve_datasets.py +164 -0
- backend/api/presets.py +1 -1
- backend/app.py +2 -1
- frontend/src/App.tsx +8 -1
- frontend/src/adaevolve/AdaevolveApp.tsx +538 -0
- frontend/src/adaevolve/api.ts +51 -0
- frontend/src/adaevolve/store.ts +180 -0
- frontend/src/adaevolve/types.ts +64 -0
backend/api/adaevolve_datasets.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib
|
| 2 |
+
from flask import Blueprint, request, jsonify
|
| 3 |
+
from datasets import load_dataset
|
| 4 |
+
|
| 5 |
+
bp = Blueprint("adaevolve_datasets", __name__, url_prefix="/api/adaevolve/datasets")
|
| 6 |
+
|
| 7 |
+
_cache: dict[str, dict] = {}
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _make_id(repo: str, split: str) -> str:
|
| 11 |
+
key = f"{repo}:{split}"
|
| 12 |
+
return hashlib.md5(key.encode()).hexdigest()[:12]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _build_iteration_summary(row: dict, idx: int) -> dict:
|
| 16 |
+
"""Build a summary for one iteration row."""
|
| 17 |
+
return {
|
| 18 |
+
"index": idx,
|
| 19 |
+
"iteration": row.get("iteration", idx),
|
| 20 |
+
"island_id": row.get("island_id", 0),
|
| 21 |
+
"score": row.get("score", 0.0),
|
| 22 |
+
"best_score": row.get("best_score", 0.0),
|
| 23 |
+
"delta": row.get("delta", 0.0),
|
| 24 |
+
"adaptation_type": row.get("adaptation_type", ""),
|
| 25 |
+
"exploration_intensity": row.get("exploration_intensity", 0.0),
|
| 26 |
+
"is_valid": row.get("is_valid", False),
|
| 27 |
+
"task_id": row.get("task_id", ""),
|
| 28 |
+
"meta_guidance_tactic": row.get("meta_guidance_tactic", ""),
|
| 29 |
+
"tactic_approach_type": row.get("tactic_approach_type", ""),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _build_summary_stats(iterations: list[dict]) -> dict:
|
| 34 |
+
"""Build aggregate stats across all iterations."""
|
| 35 |
+
adaptation_counts: dict[str, int] = {}
|
| 36 |
+
island_best_scores: dict[int, float] = {}
|
| 37 |
+
global_best = 0.0
|
| 38 |
+
|
| 39 |
+
for it in iterations:
|
| 40 |
+
atype = it.get("adaptation_type", "unknown")
|
| 41 |
+
adaptation_counts[atype] = adaptation_counts.get(atype, 0) + 1
|
| 42 |
+
|
| 43 |
+
iid = it.get("island_id", 0)
|
| 44 |
+
score = it.get("best_score", 0.0)
|
| 45 |
+
if iid not in island_best_scores or score > island_best_scores[iid]:
|
| 46 |
+
island_best_scores[iid] = score
|
| 47 |
+
|
| 48 |
+
if score > global_best:
|
| 49 |
+
global_best = score
|
| 50 |
+
|
| 51 |
+
return {
|
| 52 |
+
"adaptation_counts": adaptation_counts,
|
| 53 |
+
"island_best_scores": {str(k): v for k, v in island_best_scores.items()},
|
| 54 |
+
"global_best": global_best,
|
| 55 |
+
"n_islands": len(island_best_scores),
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@bp.route("/load", methods=["POST"])
|
| 60 |
+
def load_dataset_endpoint():
|
| 61 |
+
data = request.get_json()
|
| 62 |
+
repo = data.get("repo", "").strip()
|
| 63 |
+
if not repo:
|
| 64 |
+
return jsonify({"error": "repo is required"}), 400
|
| 65 |
+
|
| 66 |
+
split = data.get("split", "train")
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
ds = load_dataset(repo, split=split)
|
| 70 |
+
except Exception as e:
|
| 71 |
+
return jsonify({"error": f"Failed to load dataset: {e}"}), 400
|
| 72 |
+
|
| 73 |
+
ds_id = _make_id(repo, split)
|
| 74 |
+
|
| 75 |
+
# Build iteration summaries
|
| 76 |
+
iterations = []
|
| 77 |
+
for i in range(len(ds)):
|
| 78 |
+
row = ds[i]
|
| 79 |
+
summary = _build_iteration_summary(row, i)
|
| 80 |
+
iterations.append(summary)
|
| 81 |
+
|
| 82 |
+
summary_stats = _build_summary_stats(iterations)
|
| 83 |
+
|
| 84 |
+
_cache[ds_id] = {
|
| 85 |
+
"repo": repo,
|
| 86 |
+
"split": split,
|
| 87 |
+
"dataset": ds,
|
| 88 |
+
"iterations": iterations,
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
short_name = repo.rsplit("/", 1)[-1] if "/" in repo else repo
|
| 92 |
+
|
| 93 |
+
return jsonify({
|
| 94 |
+
"id": ds_id,
|
| 95 |
+
"repo": repo,
|
| 96 |
+
"name": short_name,
|
| 97 |
+
"split": split,
|
| 98 |
+
"iterations": iterations,
|
| 99 |
+
"n_iterations": len(iterations),
|
| 100 |
+
"summary_stats": summary_stats,
|
| 101 |
+
})
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@bp.route("/", methods=["GET"])
|
| 105 |
+
def list_datasets():
|
| 106 |
+
result = []
|
| 107 |
+
for ds_id, info in _cache.items():
|
| 108 |
+
iterations = info["iterations"]
|
| 109 |
+
summary_stats = _build_summary_stats(iterations)
|
| 110 |
+
result.append({
|
| 111 |
+
"id": ds_id,
|
| 112 |
+
"repo": info["repo"],
|
| 113 |
+
"name": info["repo"].rsplit("/", 1)[-1] if "/" in info["repo"] else info["repo"],
|
| 114 |
+
"split": info["split"],
|
| 115 |
+
"n_iterations": len(iterations),
|
| 116 |
+
"iterations": iterations,
|
| 117 |
+
"summary_stats": summary_stats,
|
| 118 |
+
})
|
| 119 |
+
return jsonify(result)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@bp.route("/<ds_id>/iterations", methods=["GET"])
|
| 123 |
+
def get_iterations(ds_id):
|
| 124 |
+
if ds_id not in _cache:
|
| 125 |
+
return jsonify({"error": "Dataset not loaded"}), 404
|
| 126 |
+
return jsonify(_cache[ds_id]["iterations"])
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
@bp.route("/<ds_id>/iteration/<int:idx>", methods=["GET"])
|
| 130 |
+
def get_iteration(ds_id, idx):
|
| 131 |
+
"""Get full detail for one iteration including prompt, reasoning, code."""
|
| 132 |
+
if ds_id not in _cache:
|
| 133 |
+
return jsonify({"error": "Dataset not loaded"}), 404
|
| 134 |
+
|
| 135 |
+
info = _cache[ds_id]
|
| 136 |
+
if idx < 0 or idx >= len(info["dataset"]):
|
| 137 |
+
return jsonify({"error": f"Iteration index {idx} out of range"}), 404
|
| 138 |
+
|
| 139 |
+
row = info["dataset"][idx]
|
| 140 |
+
|
| 141 |
+
return jsonify({
|
| 142 |
+
"index": idx,
|
| 143 |
+
"iteration": row.get("iteration", idx),
|
| 144 |
+
"island_id": row.get("island_id", 0),
|
| 145 |
+
"score": row.get("score", 0.0),
|
| 146 |
+
"best_score": row.get("best_score", 0.0),
|
| 147 |
+
"delta": row.get("delta", 0.0),
|
| 148 |
+
"adaptation_type": row.get("adaptation_type", ""),
|
| 149 |
+
"exploration_intensity": row.get("exploration_intensity", 0.0),
|
| 150 |
+
"is_valid": row.get("is_valid", False),
|
| 151 |
+
"task_id": row.get("task_id", ""),
|
| 152 |
+
"prompt_text": row.get("prompt_text", ""),
|
| 153 |
+
"reasoning_trace": row.get("reasoning_trace", ""),
|
| 154 |
+
"program_code": row.get("program_code", ""),
|
| 155 |
+
"meta_guidance_tactic": row.get("meta_guidance_tactic", ""),
|
| 156 |
+
"tactic_approach_type": row.get("tactic_approach_type", ""),
|
| 157 |
+
})
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
@bp.route("/<ds_id>", methods=["DELETE"])
|
| 161 |
+
def unload_dataset(ds_id):
|
| 162 |
+
if ds_id in _cache:
|
| 163 |
+
del _cache[ds_id]
|
| 164 |
+
return jsonify({"status": "ok"})
|
backend/api/presets.py
CHANGED
|
@@ -8,7 +8,7 @@ from flask import Blueprint, request, jsonify
|
|
| 8 |
bp = Blueprint("presets", __name__, url_prefix="/api/presets")
|
| 9 |
|
| 10 |
PRESETS_REPO = "reasoning-degeneration-dev/AGG_VIS_PRESETS"
|
| 11 |
-
VALID_TYPES = {"model", "arena", "rlm", "rlm-eval", "harbor"}
|
| 12 |
LOCAL_PRESETS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "presets")
|
| 13 |
|
| 14 |
# In-memory cache: vis_type -> list[dict]
|
|
|
|
| 8 |
bp = Blueprint("presets", __name__, url_prefix="/api/presets")
|
| 9 |
|
| 10 |
PRESETS_REPO = "reasoning-degeneration-dev/AGG_VIS_PRESETS"
|
| 11 |
+
VALID_TYPES = {"model", "arena", "rlm", "rlm-eval", "harbor", "adaevolve"}
|
| 12 |
LOCAL_PRESETS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "presets")
|
| 13 |
|
| 14 |
# In-memory cache: vis_type -> list[dict]
|
backend/app.py
CHANGED
|
@@ -6,12 +6,13 @@ def create_app():
|
|
| 6 |
app = Flask(__name__, static_folder="../frontend/dist", static_url_path="/")
|
| 7 |
CORS(app)
|
| 8 |
|
| 9 |
-
from backend.api import model_datasets, arena_datasets, rlm_datasets, rlm_eval_datasets, harbor_datasets, presets
|
| 10 |
app.register_blueprint(model_datasets.bp)
|
| 11 |
app.register_blueprint(arena_datasets.bp)
|
| 12 |
app.register_blueprint(rlm_datasets.bp)
|
| 13 |
app.register_blueprint(rlm_eval_datasets.bp)
|
| 14 |
app.register_blueprint(harbor_datasets.bp)
|
|
|
|
| 15 |
app.register_blueprint(presets.bp)
|
| 16 |
|
| 17 |
@app.route("/api/health")
|
|
|
|
| 6 |
app = Flask(__name__, static_folder="../frontend/dist", static_url_path="/")
|
| 7 |
CORS(app)
|
| 8 |
|
| 9 |
+
from backend.api import model_datasets, arena_datasets, rlm_datasets, rlm_eval_datasets, harbor_datasets, adaevolve_datasets, presets
|
| 10 |
app.register_blueprint(model_datasets.bp)
|
| 11 |
app.register_blueprint(arena_datasets.bp)
|
| 12 |
app.register_blueprint(rlm_datasets.bp)
|
| 13 |
app.register_blueprint(rlm_eval_datasets.bp)
|
| 14 |
app.register_blueprint(harbor_datasets.bp)
|
| 15 |
+
app.register_blueprint(adaevolve_datasets.bp)
|
| 16 |
app.register_blueprint(presets.bp)
|
| 17 |
|
| 18 |
@app.route("/api/health")
|
frontend/src/App.tsx
CHANGED
|
@@ -5,8 +5,9 @@ const ArenaApp = lazy(() => import("./arena/ArenaApp"));
|
|
| 5 |
const RlmEvalApp = lazy(() => import("./rlm-eval/RlmEvalApp"));
|
| 6 |
const RlmApp = lazy(() => import("./rlm/RlmApp"));
|
| 7 |
const HarborApp = lazy(() => import("./harbor/HarborApp"));
|
|
|
|
| 8 |
|
| 9 |
-
type TabId = "model" | "arena" | "rlm-eval" | "rlm" | "harbor";
|
| 10 |
|
| 11 |
const TABS: { id: TabId; label: string; color: string; activeClass: string }[] = [
|
| 12 |
{ id: "model", label: "Model Trace", color: "blue", activeClass: "border-blue-500 text-blue-400" },
|
|
@@ -14,6 +15,7 @@ const TABS: { id: TabId; label: string; color: string; activeClass: string }[] =
|
|
| 14 |
{ id: "rlm-eval", label: "RLM", color: "emerald", activeClass: "border-emerald-500 text-emerald-400" },
|
| 15 |
{ id: "rlm", label: "RLM+GEPA", color: "orange", activeClass: "border-orange-500 text-orange-400" },
|
| 16 |
{ id: "harbor", label: "Harbor", color: "teal", activeClass: "border-teal-500 text-teal-400" },
|
|
|
|
| 17 |
];
|
| 18 |
|
| 19 |
export default function App() {
|
|
@@ -73,6 +75,11 @@ export default function App() {
|
|
| 73 |
<HarborApp />
|
| 74 |
</div>
|
| 75 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
</Suspense>
|
| 77 |
</div>
|
| 78 |
</div>
|
|
|
|
| 5 |
const RlmEvalApp = lazy(() => import("./rlm-eval/RlmEvalApp"));
|
| 6 |
const RlmApp = lazy(() => import("./rlm/RlmApp"));
|
| 7 |
const HarborApp = lazy(() => import("./harbor/HarborApp"));
|
| 8 |
+
const AdaevolveApp = lazy(() => import("./adaevolve/AdaevolveApp"));
|
| 9 |
|
| 10 |
+
type TabId = "model" | "arena" | "rlm-eval" | "rlm" | "harbor" | "adaevolve";
|
| 11 |
|
| 12 |
const TABS: { id: TabId; label: string; color: string; activeClass: string }[] = [
|
| 13 |
{ id: "model", label: "Model Trace", color: "blue", activeClass: "border-blue-500 text-blue-400" },
|
|
|
|
| 15 |
{ id: "rlm-eval", label: "RLM", color: "emerald", activeClass: "border-emerald-500 text-emerald-400" },
|
| 16 |
{ id: "rlm", label: "RLM+GEPA", color: "orange", activeClass: "border-orange-500 text-orange-400" },
|
| 17 |
{ id: "harbor", label: "Harbor", color: "teal", activeClass: "border-teal-500 text-teal-400" },
|
| 18 |
+
{ id: "adaevolve", label: "AdaEvolve", color: "rose", activeClass: "border-rose-500 text-rose-400" },
|
| 19 |
];
|
| 20 |
|
| 21 |
export default function App() {
|
|
|
|
| 75 |
<HarborApp />
|
| 76 |
</div>
|
| 77 |
)}
|
| 78 |
+
{activeTab === "adaevolve" && (
|
| 79 |
+
<div className="theme-adaevolve h-full">
|
| 80 |
+
<AdaevolveApp />
|
| 81 |
+
</div>
|
| 82 |
+
)}
|
| 83 |
</Suspense>
|
| 84 |
</div>
|
| 85 |
</div>
|
frontend/src/adaevolve/AdaevolveApp.tsx
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useState, useMemo } from "react";
|
| 2 |
+
import { useAppState } from "./store";
|
| 3 |
+
import type { IterationSummary } from "./types";
|
| 4 |
+
|
| 5 |
+
const ADAPTATION_COLORS: Record<string, { bg: string; text: string; bar: string }> = {
|
| 6 |
+
L1_explore: { bg: "bg-blue-900/40", text: "text-blue-400", bar: "bg-blue-500" },
|
| 7 |
+
L1_exploit: { bg: "bg-green-900/40", text: "text-green-400", bar: "bg-green-500" },
|
| 8 |
+
L2_migrate: { bg: "bg-amber-900/40", text: "text-amber-400", bar: "bg-amber-500" },
|
| 9 |
+
L3_meta: { bg: "bg-red-900/40", text: "text-red-400", bar: "bg-red-500" },
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
const DEFAULT_COLOR = { bg: "bg-gray-900/40", text: "text-gray-400", bar: "bg-gray-500" };
|
| 13 |
+
|
| 14 |
+
function getAdaptationColor(type: string) {
|
| 15 |
+
return ADAPTATION_COLORS[type] || DEFAULT_COLOR;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
type SortKey = "iteration" | "score" | "best_score" | "delta" | "island_id" | "adaptation_type";
|
| 19 |
+
type SortDir = "asc" | "desc";
|
| 20 |
+
|
| 21 |
+
export default function AdaevolveApp() {
|
| 22 |
+
const store = useAppState();
|
| 23 |
+
const { state } = store;
|
| 24 |
+
const [repoInput, setRepoInput] = useState("");
|
| 25 |
+
const [sortKey, setSortKey] = useState<SortKey>("iteration");
|
| 26 |
+
const [sortDir, setSortDir] = useState<SortDir>("asc");
|
| 27 |
+
|
| 28 |
+
useEffect(() => {
|
| 29 |
+
store.loadPresets();
|
| 30 |
+
}, []);
|
| 31 |
+
|
| 32 |
+
// Get the currently selected dataset
|
| 33 |
+
const selectedDataset = useMemo(
|
| 34 |
+
() => state.datasets.find((d) => d.id === state.selectedDatasetId) ?? null,
|
| 35 |
+
[state.datasets, state.selectedDatasetId]
|
| 36 |
+
);
|
| 37 |
+
|
| 38 |
+
// Filter iterations by adaptation type
|
| 39 |
+
const filteredIterations = useMemo(() => {
|
| 40 |
+
if (!selectedDataset) return [];
|
| 41 |
+
let iters = selectedDataset.iterations;
|
| 42 |
+
if (state.filterAdaptation) {
|
| 43 |
+
iters = iters.filter((it) => it.adaptation_type === state.filterAdaptation);
|
| 44 |
+
}
|
| 45 |
+
return iters;
|
| 46 |
+
}, [selectedDataset, state.filterAdaptation]);
|
| 47 |
+
|
| 48 |
+
// Sort iterations
|
| 49 |
+
const sortedIterations = useMemo(() => {
|
| 50 |
+
const sorted = [...filteredIterations];
|
| 51 |
+
sorted.sort((a, b) => {
|
| 52 |
+
const av = a[sortKey];
|
| 53 |
+
const bv = b[sortKey];
|
| 54 |
+
if (typeof av === "number" && typeof bv === "number") {
|
| 55 |
+
return sortDir === "asc" ? av - bv : bv - av;
|
| 56 |
+
}
|
| 57 |
+
const sa = String(av);
|
| 58 |
+
const sb = String(bv);
|
| 59 |
+
return sortDir === "asc" ? sa.localeCompare(sb) : sb.localeCompare(sa);
|
| 60 |
+
});
|
| 61 |
+
return sorted;
|
| 62 |
+
}, [filteredIterations, sortKey, sortDir]);
|
| 63 |
+
|
| 64 |
+
// Get all unique adaptation types across selected dataset
|
| 65 |
+
const adaptationTypes = useMemo(() => {
|
| 66 |
+
if (!selectedDataset) return [];
|
| 67 |
+
const types = new Set(selectedDataset.iterations.map((it) => it.adaptation_type));
|
| 68 |
+
return Array.from(types).sort();
|
| 69 |
+
}, [selectedDataset]);
|
| 70 |
+
|
| 71 |
+
function handleSort(key: SortKey) {
|
| 72 |
+
if (sortKey === key) {
|
| 73 |
+
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
|
| 74 |
+
} else {
|
| 75 |
+
setSortKey(key);
|
| 76 |
+
setSortDir("asc");
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
function handleLoad() {
|
| 81 |
+
if (repoInput.trim()) {
|
| 82 |
+
store.loadDataset(repoInput.trim());
|
| 83 |
+
setRepoInput("");
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
// Compute max score for bar chart scaling
|
| 88 |
+
const maxScore = useMemo(() => {
|
| 89 |
+
if (!filteredIterations.length) return 1;
|
| 90 |
+
return Math.max(...filteredIterations.map((it) => Math.abs(it.score)), 0.001);
|
| 91 |
+
}, [filteredIterations]);
|
| 92 |
+
|
| 93 |
+
const sortIndicator = (key: SortKey) => {
|
| 94 |
+
if (sortKey !== key) return "";
|
| 95 |
+
return sortDir === "asc" ? " ^" : " v";
|
| 96 |
+
};
|
| 97 |
+
|
| 98 |
+
return (
|
| 99 |
+
<div className="flex h-full overflow-hidden">
|
| 100 |
+
{/* Sidebar */}
|
| 101 |
+
<div className="w-72 border-r border-gray-800 bg-gray-900 flex flex-col overflow-hidden shrink-0">
|
| 102 |
+
{/* Repo input */}
|
| 103 |
+
<div className="p-3 border-b border-gray-800">
|
| 104 |
+
<label className="text-xs text-gray-500 mb-1 block">HuggingFace Repo</label>
|
| 105 |
+
<div className="flex gap-1">
|
| 106 |
+
<input
|
| 107 |
+
type="text"
|
| 108 |
+
value={repoInput}
|
| 109 |
+
onChange={(e) => setRepoInput(e.target.value)}
|
| 110 |
+
onKeyDown={(e) => e.key === "Enter" && handleLoad()}
|
| 111 |
+
placeholder="org/dataset-name"
|
| 112 |
+
className="flex-1 bg-gray-800 text-sm px-2 py-1.5 rounded border border-gray-700 focus:border-rose-500 focus:outline-none text-gray-200"
|
| 113 |
+
/>
|
| 114 |
+
<button
|
| 115 |
+
onClick={handleLoad}
|
| 116 |
+
disabled={state.loading || !repoInput.trim()}
|
| 117 |
+
className="bg-rose-600 hover:bg-rose-500 disabled:opacity-50 text-white text-sm px-3 py-1.5 rounded"
|
| 118 |
+
>
|
| 119 |
+
Load
|
| 120 |
+
</button>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
{/* Presets */}
|
| 125 |
+
<div className="p-3 border-b border-gray-800">
|
| 126 |
+
<div className="text-xs text-gray-500 mb-2">Presets</div>
|
| 127 |
+
{state.presets.length === 0 && (
|
| 128 |
+
<div className="text-xs text-gray-600">No presets available</div>
|
| 129 |
+
)}
|
| 130 |
+
<div className="space-y-1 max-h-32 overflow-y-auto">
|
| 131 |
+
{state.presets.map((p) => (
|
| 132 |
+
<div
|
| 133 |
+
key={p.id}
|
| 134 |
+
className="flex items-center justify-between group"
|
| 135 |
+
>
|
| 136 |
+
<button
|
| 137 |
+
onClick={() => store.loadPreset(p)}
|
| 138 |
+
className="text-xs text-rose-400 hover:text-rose-300 truncate flex-1 text-left"
|
| 139 |
+
title={p.repo}
|
| 140 |
+
>
|
| 141 |
+
{p.name}
|
| 142 |
+
</button>
|
| 143 |
+
<button
|
| 144 |
+
onClick={() => store.deletePreset(p.id)}
|
| 145 |
+
className="text-gray-600 hover:text-red-400 text-xs opacity-0 group-hover:opacity-100 ml-1"
|
| 146 |
+
>
|
| 147 |
+
x
|
| 148 |
+
</button>
|
| 149 |
+
</div>
|
| 150 |
+
))}
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
{/* Loaded datasets */}
|
| 155 |
+
<div className="flex-1 overflow-y-auto p-3">
|
| 156 |
+
<div className="text-xs text-gray-500 mb-2">
|
| 157 |
+
Loaded Datasets ({state.datasets.length})
|
| 158 |
+
</div>
|
| 159 |
+
{state.datasets.map((ds) => (
|
| 160 |
+
<div
|
| 161 |
+
key={ds.id}
|
| 162 |
+
className={`mb-3 p-2 rounded border cursor-pointer ${
|
| 163 |
+
state.selectedDatasetId === ds.id
|
| 164 |
+
? "border-rose-500 bg-rose-900/20"
|
| 165 |
+
: "border-gray-700 hover:border-gray-600"
|
| 166 |
+
}`}
|
| 167 |
+
onClick={() => store.selectDataset(ds.id)}
|
| 168 |
+
>
|
| 169 |
+
<div className="flex items-center justify-between">
|
| 170 |
+
<span className="text-sm text-gray-200 truncate" title={ds.repo}>
|
| 171 |
+
{ds.name}
|
| 172 |
+
</span>
|
| 173 |
+
<button
|
| 174 |
+
onClick={(e) => {
|
| 175 |
+
e.stopPropagation();
|
| 176 |
+
store.unloadDataset(ds.id);
|
| 177 |
+
}}
|
| 178 |
+
className="text-gray-600 hover:text-red-400 text-xs ml-1"
|
| 179 |
+
>
|
| 180 |
+
x
|
| 181 |
+
</button>
|
| 182 |
+
</div>
|
| 183 |
+
<div className="text-xs text-gray-500 mt-1">
|
| 184 |
+
{ds.n_iterations} iterations | {ds.summary_stats.n_islands} islands
|
| 185 |
+
</div>
|
| 186 |
+
<div className="text-xs text-gray-500">
|
| 187 |
+
Best: {ds.summary_stats.global_best.toFixed(4)}
|
| 188 |
+
</div>
|
| 189 |
+
{/* Adaptation breakdown */}
|
| 190 |
+
<div className="mt-1 space-y-0.5">
|
| 191 |
+
{Object.entries(ds.summary_stats.adaptation_counts).map(([type, count]) => {
|
| 192 |
+
const color = getAdaptationColor(type);
|
| 193 |
+
return (
|
| 194 |
+
<div key={type} className="flex items-center text-xs gap-1">
|
| 195 |
+
<span className={`w-2 h-2 rounded-full ${color.bar}`} />
|
| 196 |
+
<span className={color.text}>{type}</span>
|
| 197 |
+
<span className="text-gray-600 ml-auto">{count}</span>
|
| 198 |
+
</div>
|
| 199 |
+
);
|
| 200 |
+
})}
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
))}
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
+
{/* Filter by adaptation type */}
|
| 207 |
+
{selectedDataset && (
|
| 208 |
+
<div className="p-3 border-t border-gray-800">
|
| 209 |
+
<div className="text-xs text-gray-500 mb-1">Filter by Adaptation</div>
|
| 210 |
+
<select
|
| 211 |
+
value={state.filterAdaptation}
|
| 212 |
+
onChange={(e) => store.setFilterAdaptation(e.target.value)}
|
| 213 |
+
className="w-full bg-gray-800 text-sm px-2 py-1 rounded border border-gray-700 text-gray-200"
|
| 214 |
+
>
|
| 215 |
+
<option value="">All</option>
|
| 216 |
+
{adaptationTypes.map((t) => (
|
| 217 |
+
<option key={t} value={t}>
|
| 218 |
+
{t}
|
| 219 |
+
</option>
|
| 220 |
+
))}
|
| 221 |
+
</select>
|
| 222 |
+
</div>
|
| 223 |
+
)}
|
| 224 |
+
</div>
|
| 225 |
+
|
| 226 |
+
{/* Main content */}
|
| 227 |
+
<div className="flex-1 flex flex-col overflow-hidden">
|
| 228 |
+
{state.error && (
|
| 229 |
+
<div className="bg-red-900/50 border-b border-red-700 px-4 py-2 text-sm text-red-200 flex justify-between items-center">
|
| 230 |
+
<span>{state.error}</span>
|
| 231 |
+
<button
|
| 232 |
+
onClick={() => store.setError(null)}
|
| 233 |
+
className="text-red-400 hover:text-red-200 ml-4"
|
| 234 |
+
>
|
| 235 |
+
dismiss
|
| 236 |
+
</button>
|
| 237 |
+
</div>
|
| 238 |
+
)}
|
| 239 |
+
|
| 240 |
+
{state.loading && (
|
| 241 |
+
<div className="bg-rose-900/30 border-b border-rose-800 px-4 py-1.5 text-xs text-rose-300">
|
| 242 |
+
Loading...
|
| 243 |
+
</div>
|
| 244 |
+
)}
|
| 245 |
+
|
| 246 |
+
{/* Timeline view */}
|
| 247 |
+
{state.viewMode === "timeline" && selectedDataset && (
|
| 248 |
+
<div className="flex-1 flex flex-col overflow-hidden">
|
| 249 |
+
{/* Bar chart */}
|
| 250 |
+
<div className="border-b border-gray-800 p-4 shrink-0">
|
| 251 |
+
<div className="text-sm text-gray-400 mb-2">
|
| 252 |
+
Score Timeline ({filteredIterations.length} iterations)
|
| 253 |
+
</div>
|
| 254 |
+
<div className="flex items-end gap-px h-32 overflow-x-auto">
|
| 255 |
+
{filteredIterations.map((it) => {
|
| 256 |
+
const height = Math.max((Math.abs(it.score) / maxScore) * 100, 2);
|
| 257 |
+
const color = getAdaptationColor(it.adaptation_type);
|
| 258 |
+
return (
|
| 259 |
+
<div
|
| 260 |
+
key={it.index}
|
| 261 |
+
className="flex flex-col items-center justify-end flex-shrink-0 cursor-pointer group"
|
| 262 |
+
style={{ minWidth: Math.max(4, Math.min(20, 800 / filteredIterations.length)) }}
|
| 263 |
+
onClick={() => store.selectIteration(selectedDataset.id, it.index)}
|
| 264 |
+
title={`iter ${it.iteration} | score ${it.score.toFixed(4)} | ${it.adaptation_type}`}
|
| 265 |
+
>
|
| 266 |
+
<div
|
| 267 |
+
className={`w-full rounded-t ${color.bar} group-hover:opacity-80 transition-opacity`}
|
| 268 |
+
style={{ height: `${height}%` }}
|
| 269 |
+
/>
|
| 270 |
+
</div>
|
| 271 |
+
);
|
| 272 |
+
})}
|
| 273 |
+
</div>
|
| 274 |
+
{/* Legend */}
|
| 275 |
+
<div className="flex gap-4 mt-2">
|
| 276 |
+
{Object.entries(ADAPTATION_COLORS).map(([type, color]) => (
|
| 277 |
+
<div key={type} className="flex items-center gap-1 text-xs">
|
| 278 |
+
<span className={`w-2 h-2 rounded-full ${color.bar}`} />
|
| 279 |
+
<span className={color.text}>{type}</span>
|
| 280 |
+
</div>
|
| 281 |
+
))}
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
{/* Iteration table */}
|
| 286 |
+
<div className="flex-1 overflow-auto">
|
| 287 |
+
<table className="w-full text-sm">
|
| 288 |
+
<thead className="sticky top-0 bg-gray-900 border-b border-gray-800">
|
| 289 |
+
<tr>
|
| 290 |
+
{(
|
| 291 |
+
[
|
| 292 |
+
["iteration", "Iter"],
|
| 293 |
+
["island_id", "Island"],
|
| 294 |
+
["score", "Score"],
|
| 295 |
+
["best_score", "Best"],
|
| 296 |
+
["delta", "Delta"],
|
| 297 |
+
["adaptation_type", "Adaptation"],
|
| 298 |
+
] as [SortKey, string][]
|
| 299 |
+
).map(([key, label]) => (
|
| 300 |
+
<th
|
| 301 |
+
key={key}
|
| 302 |
+
onClick={() => handleSort(key)}
|
| 303 |
+
className="px-3 py-2 text-left text-gray-400 font-medium cursor-pointer hover:text-gray-200 select-none"
|
| 304 |
+
>
|
| 305 |
+
{label}
|
| 306 |
+
{sortIndicator(key)}
|
| 307 |
+
</th>
|
| 308 |
+
))}
|
| 309 |
+
<th className="px-3 py-2 text-left text-gray-400 font-medium">Valid</th>
|
| 310 |
+
<th className="px-3 py-2 text-left text-gray-400 font-medium">Task</th>
|
| 311 |
+
</tr>
|
| 312 |
+
</thead>
|
| 313 |
+
<tbody>
|
| 314 |
+
{sortedIterations.map((it) => {
|
| 315 |
+
const color = getAdaptationColor(it.adaptation_type);
|
| 316 |
+
return (
|
| 317 |
+
<tr
|
| 318 |
+
key={it.index}
|
| 319 |
+
onClick={() => store.selectIteration(selectedDataset.id, it.index)}
|
| 320 |
+
className="border-b border-gray-800/50 hover:bg-gray-800/50 cursor-pointer"
|
| 321 |
+
>
|
| 322 |
+
<td className="px-3 py-1.5 text-gray-300">{it.iteration}</td>
|
| 323 |
+
<td className="px-3 py-1.5 text-gray-300">{it.island_id}</td>
|
| 324 |
+
<td className="px-3 py-1.5 text-gray-200 font-mono">
|
| 325 |
+
{it.score.toFixed(4)}
|
| 326 |
+
</td>
|
| 327 |
+
<td className="px-3 py-1.5 text-gray-200 font-mono">
|
| 328 |
+
{it.best_score.toFixed(4)}
|
| 329 |
+
</td>
|
| 330 |
+
<td
|
| 331 |
+
className={`px-3 py-1.5 font-mono ${
|
| 332 |
+
it.delta > 0
|
| 333 |
+
? "text-green-400"
|
| 334 |
+
: it.delta < 0
|
| 335 |
+
? "text-red-400"
|
| 336 |
+
: "text-gray-500"
|
| 337 |
+
}`}
|
| 338 |
+
>
|
| 339 |
+
{it.delta > 0 ? "+" : ""}
|
| 340 |
+
{it.delta.toFixed(4)}
|
| 341 |
+
</td>
|
| 342 |
+
<td className="px-3 py-1.5">
|
| 343 |
+
<span
|
| 344 |
+
className={`px-1.5 py-0.5 rounded text-xs ${color.bg} ${color.text}`}
|
| 345 |
+
>
|
| 346 |
+
{it.adaptation_type}
|
| 347 |
+
</span>
|
| 348 |
+
</td>
|
| 349 |
+
<td className="px-3 py-1.5">
|
| 350 |
+
{it.is_valid ? (
|
| 351 |
+
<span className="text-green-400 text-xs">yes</span>
|
| 352 |
+
) : (
|
| 353 |
+
<span className="text-red-400 text-xs">no</span>
|
| 354 |
+
)}
|
| 355 |
+
</td>
|
| 356 |
+
<td className="px-3 py-1.5 text-gray-500 text-xs truncate max-w-[150px]">
|
| 357 |
+
{it.task_id}
|
| 358 |
+
</td>
|
| 359 |
+
</tr>
|
| 360 |
+
);
|
| 361 |
+
})}
|
| 362 |
+
</tbody>
|
| 363 |
+
</table>
|
| 364 |
+
</div>
|
| 365 |
+
</div>
|
| 366 |
+
)}
|
| 367 |
+
|
| 368 |
+
{/* Detail view */}
|
| 369 |
+
{state.viewMode === "detail" && state.iterationDetail && (
|
| 370 |
+
<div className="flex-1 flex flex-col overflow-hidden">
|
| 371 |
+
{/* Detail header */}
|
| 372 |
+
<div className="border-b border-gray-800 px-4 py-2 flex items-center gap-4 shrink-0">
|
| 373 |
+
<button
|
| 374 |
+
onClick={() => store.backToTimeline()}
|
| 375 |
+
className="text-rose-400 hover:text-rose-300 text-sm"
|
| 376 |
+
>
|
| 377 |
+
← Back to timeline
|
| 378 |
+
</button>
|
| 379 |
+
<div className="text-sm text-gray-300">
|
| 380 |
+
Iteration {state.iterationDetail.iteration} | Island{" "}
|
| 381 |
+
{state.iterationDetail.island_id}
|
| 382 |
+
</div>
|
| 383 |
+
<div className="flex items-center gap-2 ml-auto">
|
| 384 |
+
<span
|
| 385 |
+
className={`px-2 py-0.5 rounded text-xs ${
|
| 386 |
+
getAdaptationColor(state.iterationDetail.adaptation_type).bg
|
| 387 |
+
} ${getAdaptationColor(state.iterationDetail.adaptation_type).text}`}
|
| 388 |
+
>
|
| 389 |
+
{state.iterationDetail.adaptation_type}
|
| 390 |
+
</span>
|
| 391 |
+
<span className="text-gray-400 text-sm font-mono">
|
| 392 |
+
score: {state.iterationDetail.score.toFixed(4)}
|
| 393 |
+
</span>
|
| 394 |
+
<span
|
| 395 |
+
className={`text-sm font-mono ${
|
| 396 |
+
state.iterationDetail.delta > 0
|
| 397 |
+
? "text-green-400"
|
| 398 |
+
: state.iterationDetail.delta < 0
|
| 399 |
+
? "text-red-400"
|
| 400 |
+
: "text-gray-500"
|
| 401 |
+
}`}
|
| 402 |
+
>
|
| 403 |
+
({state.iterationDetail.delta > 0 ? "+" : ""}
|
| 404 |
+
{state.iterationDetail.delta.toFixed(4)})
|
| 405 |
+
</span>
|
| 406 |
+
{state.iterationDetail.is_valid ? (
|
| 407 |
+
<span className="text-green-400 text-xs border border-green-800 rounded px-1">
|
| 408 |
+
valid
|
| 409 |
+
</span>
|
| 410 |
+
) : (
|
| 411 |
+
<span className="text-red-400 text-xs border border-red-800 rounded px-1">
|
| 412 |
+
invalid
|
| 413 |
+
</span>
|
| 414 |
+
)}
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
|
| 418 |
+
{/* Detail grid */}
|
| 419 |
+
<div className="flex-1 overflow-auto p-4">
|
| 420 |
+
<div className="grid grid-cols-2 gap-4 h-full">
|
| 421 |
+
{/* Meta info */}
|
| 422 |
+
<div className="col-span-2 grid grid-cols-4 gap-3">
|
| 423 |
+
<div className="bg-gray-900 rounded border border-gray-800 p-3">
|
| 424 |
+
<div className="text-xs text-gray-500 mb-1">Task ID</div>
|
| 425 |
+
<div className="text-sm text-gray-200 break-all">
|
| 426 |
+
{state.iterationDetail.task_id || "-"}
|
| 427 |
+
</div>
|
| 428 |
+
</div>
|
| 429 |
+
<div className="bg-gray-900 rounded border border-gray-800 p-3">
|
| 430 |
+
<div className="text-xs text-gray-500 mb-1">Exploration Intensity</div>
|
| 431 |
+
<div className="text-sm text-gray-200 font-mono">
|
| 432 |
+
{state.iterationDetail.exploration_intensity.toFixed(4)}
|
| 433 |
+
</div>
|
| 434 |
+
</div>
|
| 435 |
+
<div className="bg-gray-900 rounded border border-gray-800 p-3">
|
| 436 |
+
<div className="text-xs text-gray-500 mb-1">Meta-Guidance Tactic</div>
|
| 437 |
+
<div className="text-sm text-gray-200">
|
| 438 |
+
{state.iterationDetail.meta_guidance_tactic || "-"}
|
| 439 |
+
</div>
|
| 440 |
+
</div>
|
| 441 |
+
<div className="bg-gray-900 rounded border border-gray-800 p-3">
|
| 442 |
+
<div className="text-xs text-gray-500 mb-1">Tactic Approach</div>
|
| 443 |
+
<div className="text-sm text-gray-200">
|
| 444 |
+
{state.iterationDetail.tactic_approach_type || "-"}
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
|
| 449 |
+
{/* Prompt */}
|
| 450 |
+
<div className="bg-gray-900 rounded border border-gray-800 flex flex-col overflow-hidden">
|
| 451 |
+
<div className="px-3 py-2 border-b border-gray-800 text-xs text-rose-400 font-medium shrink-0">
|
| 452 |
+
Prompt
|
| 453 |
+
</div>
|
| 454 |
+
<pre className="flex-1 overflow-auto p-3 text-xs text-gray-300 whitespace-pre-wrap font-mono">
|
| 455 |
+
{state.iterationDetail.prompt_text || "(empty)"}
|
| 456 |
+
</pre>
|
| 457 |
+
</div>
|
| 458 |
+
|
| 459 |
+
{/* Reasoning trace */}
|
| 460 |
+
<div className="bg-gray-900 rounded border border-gray-800 flex flex-col overflow-hidden">
|
| 461 |
+
<div className="px-3 py-2 border-b border-gray-800 text-xs text-rose-400 font-medium shrink-0">
|
| 462 |
+
Reasoning Trace
|
| 463 |
+
</div>
|
| 464 |
+
<pre className="flex-1 overflow-auto p-3 text-xs text-gray-300 whitespace-pre-wrap font-mono">
|
| 465 |
+
{state.iterationDetail.reasoning_trace || "(empty)"}
|
| 466 |
+
</pre>
|
| 467 |
+
</div>
|
| 468 |
+
|
| 469 |
+
{/* Generated code */}
|
| 470 |
+
<div className="col-span-2 bg-gray-900 rounded border border-gray-800 flex flex-col overflow-hidden max-h-96">
|
| 471 |
+
<div className="px-3 py-2 border-b border-gray-800 text-xs text-rose-400 font-medium shrink-0">
|
| 472 |
+
Generated Code
|
| 473 |
+
</div>
|
| 474 |
+
<pre className="flex-1 overflow-auto p-3 text-xs text-gray-300 whitespace-pre-wrap font-mono">
|
| 475 |
+
{state.iterationDetail.program_code || "(empty)"}
|
| 476 |
+
</pre>
|
| 477 |
+
</div>
|
| 478 |
+
</div>
|
| 479 |
+
</div>
|
| 480 |
+
|
| 481 |
+
{/* Navigation */}
|
| 482 |
+
{selectedDataset && (
|
| 483 |
+
<div className="border-t border-gray-800 px-4 py-2 flex items-center justify-between shrink-0">
|
| 484 |
+
<button
|
| 485 |
+
onClick={() => {
|
| 486 |
+
const idx = state.iterationDetail!.index;
|
| 487 |
+
if (idx > 0) store.selectIteration(selectedDataset.id, idx - 1);
|
| 488 |
+
}}
|
| 489 |
+
disabled={state.iterationDetail.index <= 0}
|
| 490 |
+
className="text-sm text-rose-400 hover:text-rose-300 disabled:opacity-30 disabled:cursor-default"
|
| 491 |
+
>
|
| 492 |
+
← Previous
|
| 493 |
+
</button>
|
| 494 |
+
<span className="text-xs text-gray-500">
|
| 495 |
+
{state.iterationDetail.index + 1} / {selectedDataset.n_iterations}
|
| 496 |
+
</span>
|
| 497 |
+
<button
|
| 498 |
+
onClick={() => {
|
| 499 |
+
const idx = state.iterationDetail!.index;
|
| 500 |
+
if (idx < selectedDataset.n_iterations - 1)
|
| 501 |
+
store.selectIteration(selectedDataset.id, idx + 1);
|
| 502 |
+
}}
|
| 503 |
+
disabled={
|
| 504 |
+
state.iterationDetail.index >= selectedDataset.n_iterations - 1
|
| 505 |
+
}
|
| 506 |
+
className="text-sm text-rose-400 hover:text-rose-300 disabled:opacity-30 disabled:cursor-default"
|
| 507 |
+
>
|
| 508 |
+
Next →
|
| 509 |
+
</button>
|
| 510 |
+
</div>
|
| 511 |
+
)}
|
| 512 |
+
</div>
|
| 513 |
+
)}
|
| 514 |
+
|
| 515 |
+
{/* Empty state */}
|
| 516 |
+
{state.datasets.length === 0 && state.viewMode === "timeline" && (
|
| 517 |
+
<div className="flex-1 flex items-center justify-center text-gray-500">
|
| 518 |
+
<div className="text-center">
|
| 519 |
+
<div className="text-lg mb-2">No datasets loaded</div>
|
| 520 |
+
<div className="text-sm">
|
| 521 |
+
Load a HuggingFace repo from the sidebar or select a preset
|
| 522 |
+
</div>
|
| 523 |
+
</div>
|
| 524 |
+
</div>
|
| 525 |
+
)}
|
| 526 |
+
|
| 527 |
+
{state.datasets.length > 0 && !selectedDataset && state.viewMode === "timeline" && (
|
| 528 |
+
<div className="flex-1 flex items-center justify-center text-gray-500">
|
| 529 |
+
<div className="text-center">
|
| 530 |
+
<div className="text-lg mb-2">Select a dataset</div>
|
| 531 |
+
<div className="text-sm">Click a dataset in the sidebar to view its iterations</div>
|
| 532 |
+
</div>
|
| 533 |
+
</div>
|
| 534 |
+
)}
|
| 535 |
+
</div>
|
| 536 |
+
</div>
|
| 537 |
+
);
|
| 538 |
+
}
|
frontend/src/adaevolve/api.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { DatasetInfo, IterationDetail, Preset } from "./types";
|
| 2 |
+
|
| 3 |
+
const BASE = "/api/adaevolve";
|
| 4 |
+
const PRESETS_BASE = "/api/presets/adaevolve";
|
| 5 |
+
|
| 6 |
+
async function fetchJson<T>(url: string, opts?: RequestInit): Promise<T> {
|
| 7 |
+
const res = await fetch(url, opts);
|
| 8 |
+
if (!res.ok) {
|
| 9 |
+
const body = await res.json().catch(() => ({}));
|
| 10 |
+
throw new Error(body.error || `HTTP ${res.status}`);
|
| 11 |
+
}
|
| 12 |
+
return res.json();
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// Datasets
|
| 16 |
+
export async function loadDataset(repo: string, split = "train"): Promise<DatasetInfo> {
|
| 17 |
+
return fetchJson(`${BASE}/datasets/load`, {
|
| 18 |
+
method: "POST",
|
| 19 |
+
headers: { "Content-Type": "application/json" },
|
| 20 |
+
body: JSON.stringify({ repo, split }),
|
| 21 |
+
});
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
export async function listDatasets(): Promise<DatasetInfo[]> {
|
| 25 |
+
return fetchJson(`${BASE}/datasets/`);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
export async function getIterationDetail(dsId: string, idx: number): Promise<IterationDetail> {
|
| 29 |
+
return fetchJson(`${BASE}/datasets/${dsId}/iteration/${idx}`);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export async function unloadDataset(dsId: string): Promise<void> {
|
| 33 |
+
await fetchJson(`${BASE}/datasets/${dsId}`, { method: "DELETE" });
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// Presets
|
| 37 |
+
export async function listPresets(): Promise<Preset[]> {
|
| 38 |
+
return fetchJson(PRESETS_BASE);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
export async function createPreset(name: string, repo: string, split = "train"): Promise<Preset> {
|
| 42 |
+
return fetchJson(PRESETS_BASE, {
|
| 43 |
+
method: "POST",
|
| 44 |
+
headers: { "Content-Type": "application/json" },
|
| 45 |
+
body: JSON.stringify({ name, repo, split }),
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
export async function deletePreset(id: string): Promise<void> {
|
| 50 |
+
await fetchJson(`${PRESETS_BASE}/${id}`, { method: "DELETE" });
|
| 51 |
+
}
|
frontend/src/adaevolve/store.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useState } from "react";
|
| 2 |
+
import type {
|
| 3 |
+
DatasetInfo,
|
| 4 |
+
IterationDetail,
|
| 5 |
+
Preset,
|
| 6 |
+
ViewMode,
|
| 7 |
+
} from "./types";
|
| 8 |
+
import * as api from "./api";
|
| 9 |
+
|
| 10 |
+
export interface AppState {
|
| 11 |
+
datasets: DatasetInfo[];
|
| 12 |
+
presets: Preset[];
|
| 13 |
+
selectedDatasetId: string | null;
|
| 14 |
+
selectedIterationIdx: number | null;
|
| 15 |
+
iterationDetail: IterationDetail | null;
|
| 16 |
+
viewMode: ViewMode;
|
| 17 |
+
loading: boolean;
|
| 18 |
+
error: string | null;
|
| 19 |
+
filterAdaptation: string; // "" means all
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const initialState: AppState = {
|
| 23 |
+
datasets: [],
|
| 24 |
+
presets: [],
|
| 25 |
+
selectedDatasetId: null,
|
| 26 |
+
selectedIterationIdx: null,
|
| 27 |
+
iterationDetail: null,
|
| 28 |
+
viewMode: "timeline",
|
| 29 |
+
loading: false,
|
| 30 |
+
error: null,
|
| 31 |
+
filterAdaptation: "",
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
export function useAppState() {
|
| 35 |
+
const [state, setState] = useState<AppState>(initialState);
|
| 36 |
+
|
| 37 |
+
const setError = useCallback((error: string | null) => {
|
| 38 |
+
setState((s) => ({ ...s, error }));
|
| 39 |
+
}, []);
|
| 40 |
+
|
| 41 |
+
const loadDataset = useCallback(async (repo: string, split = "train") => {
|
| 42 |
+
setState((s) => ({ ...s, loading: true, error: null }));
|
| 43 |
+
try {
|
| 44 |
+
const ds = await api.loadDataset(repo, split);
|
| 45 |
+
setState((s) => {
|
| 46 |
+
const exists = s.datasets.find((d) => d.id === ds.id);
|
| 47 |
+
const datasets = exists
|
| 48 |
+
? s.datasets.map((d) => (d.id === ds.id ? ds : d))
|
| 49 |
+
: [...s.datasets, ds];
|
| 50 |
+
const selectedDatasetId = ds.id;
|
| 51 |
+
return {
|
| 52 |
+
...s,
|
| 53 |
+
datasets,
|
| 54 |
+
selectedDatasetId,
|
| 55 |
+
loading: false,
|
| 56 |
+
};
|
| 57 |
+
});
|
| 58 |
+
} catch (e: unknown) {
|
| 59 |
+
setState((s) => ({
|
| 60 |
+
...s,
|
| 61 |
+
loading: false,
|
| 62 |
+
error: e instanceof Error ? e.message : String(e),
|
| 63 |
+
}));
|
| 64 |
+
}
|
| 65 |
+
}, []);
|
| 66 |
+
|
| 67 |
+
const unloadDataset = useCallback(async (dsId: string) => {
|
| 68 |
+
try {
|
| 69 |
+
await api.unloadDataset(dsId);
|
| 70 |
+
setState((s) => {
|
| 71 |
+
const datasets = s.datasets.filter((d) => d.id !== dsId);
|
| 72 |
+
return {
|
| 73 |
+
...s,
|
| 74 |
+
datasets,
|
| 75 |
+
selectedDatasetId: s.selectedDatasetId === dsId ? null : s.selectedDatasetId,
|
| 76 |
+
selectedIterationIdx: s.selectedDatasetId === dsId ? null : s.selectedIterationIdx,
|
| 77 |
+
iterationDetail: s.selectedDatasetId === dsId ? null : s.iterationDetail,
|
| 78 |
+
};
|
| 79 |
+
});
|
| 80 |
+
} catch (e: unknown) {
|
| 81 |
+
setState((s) => ({
|
| 82 |
+
...s,
|
| 83 |
+
error: e instanceof Error ? e.message : String(e),
|
| 84 |
+
}));
|
| 85 |
+
}
|
| 86 |
+
}, []);
|
| 87 |
+
|
| 88 |
+
const selectDataset = useCallback((dsId: string | null) => {
|
| 89 |
+
setState((s) => ({
|
| 90 |
+
...s,
|
| 91 |
+
selectedDatasetId: dsId,
|
| 92 |
+
selectedIterationIdx: null,
|
| 93 |
+
iterationDetail: null,
|
| 94 |
+
viewMode: "timeline",
|
| 95 |
+
}));
|
| 96 |
+
}, []);
|
| 97 |
+
|
| 98 |
+
const selectIteration = useCallback(async (dsId: string, idx: number) => {
|
| 99 |
+
setState((s) => ({ ...s, loading: true, error: null }));
|
| 100 |
+
try {
|
| 101 |
+
const detail = await api.getIterationDetail(dsId, idx);
|
| 102 |
+
setState((s) => ({
|
| 103 |
+
...s,
|
| 104 |
+
selectedDatasetId: dsId,
|
| 105 |
+
selectedIterationIdx: idx,
|
| 106 |
+
iterationDetail: detail,
|
| 107 |
+
viewMode: "detail",
|
| 108 |
+
loading: false,
|
| 109 |
+
}));
|
| 110 |
+
} catch (e: unknown) {
|
| 111 |
+
setState((s) => ({
|
| 112 |
+
...s,
|
| 113 |
+
loading: false,
|
| 114 |
+
error: e instanceof Error ? e.message : String(e),
|
| 115 |
+
}));
|
| 116 |
+
}
|
| 117 |
+
}, []);
|
| 118 |
+
|
| 119 |
+
const backToTimeline = useCallback(() => {
|
| 120 |
+
setState((s) => ({
|
| 121 |
+
...s,
|
| 122 |
+
selectedIterationIdx: null,
|
| 123 |
+
iterationDetail: null,
|
| 124 |
+
viewMode: "timeline",
|
| 125 |
+
}));
|
| 126 |
+
}, []);
|
| 127 |
+
|
| 128 |
+
const loadPresets = useCallback(async () => {
|
| 129 |
+
try {
|
| 130 |
+
const presets = await api.listPresets();
|
| 131 |
+
setState((s) => ({ ...s, presets }));
|
| 132 |
+
} catch {
|
| 133 |
+
// Presets might not exist yet
|
| 134 |
+
}
|
| 135 |
+
}, []);
|
| 136 |
+
|
| 137 |
+
const createPreset = useCallback(
|
| 138 |
+
async (name: string, repo: string, split = "train") => {
|
| 139 |
+
const preset = await api.createPreset(name, repo, split);
|
| 140 |
+
setState((s) => ({ ...s, presets: [...s.presets, preset] }));
|
| 141 |
+
},
|
| 142 |
+
[]
|
| 143 |
+
);
|
| 144 |
+
|
| 145 |
+
const deletePreset = useCallback(async (id: string) => {
|
| 146 |
+
await api.deletePreset(id);
|
| 147 |
+
setState((s) => ({ ...s, presets: s.presets.filter((p) => p.id !== id) }));
|
| 148 |
+
}, []);
|
| 149 |
+
|
| 150 |
+
const loadPreset = useCallback(
|
| 151 |
+
async (preset: Preset) => {
|
| 152 |
+
await loadDataset(preset.repo, preset.split);
|
| 153 |
+
},
|
| 154 |
+
[loadDataset]
|
| 155 |
+
);
|
| 156 |
+
|
| 157 |
+
const setViewMode = useCallback((viewMode: ViewMode) => {
|
| 158 |
+
setState((s) => ({ ...s, viewMode }));
|
| 159 |
+
}, []);
|
| 160 |
+
|
| 161 |
+
const setFilterAdaptation = useCallback((filterAdaptation: string) => {
|
| 162 |
+
setState((s) => ({ ...s, filterAdaptation }));
|
| 163 |
+
}, []);
|
| 164 |
+
|
| 165 |
+
return {
|
| 166 |
+
state,
|
| 167 |
+
loadDataset,
|
| 168 |
+
unloadDataset,
|
| 169 |
+
selectDataset,
|
| 170 |
+
selectIteration,
|
| 171 |
+
backToTimeline,
|
| 172 |
+
loadPresets,
|
| 173 |
+
createPreset,
|
| 174 |
+
deletePreset,
|
| 175 |
+
loadPreset,
|
| 176 |
+
setViewMode,
|
| 177 |
+
setFilterAdaptation,
|
| 178 |
+
setError,
|
| 179 |
+
};
|
| 180 |
+
}
|
frontend/src/adaevolve/types.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Iteration summary (list level)
|
| 2 |
+
export interface IterationSummary {
|
| 3 |
+
index: number;
|
| 4 |
+
iteration: number;
|
| 5 |
+
island_id: number;
|
| 6 |
+
score: number;
|
| 7 |
+
best_score: number;
|
| 8 |
+
delta: number;
|
| 9 |
+
adaptation_type: string;
|
| 10 |
+
exploration_intensity: number;
|
| 11 |
+
is_valid: boolean;
|
| 12 |
+
task_id: string;
|
| 13 |
+
meta_guidance_tactic: string;
|
| 14 |
+
tactic_approach_type: string;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
// Summary stats
|
| 18 |
+
export interface SummaryStats {
|
| 19 |
+
adaptation_counts: Record<string, number>;
|
| 20 |
+
island_best_scores: Record<string, number>;
|
| 21 |
+
global_best: number;
|
| 22 |
+
n_islands: number;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
// Dataset / repo level
|
| 26 |
+
export interface DatasetInfo {
|
| 27 |
+
id: string;
|
| 28 |
+
repo: string;
|
| 29 |
+
name: string;
|
| 30 |
+
split: string;
|
| 31 |
+
iterations: IterationSummary[];
|
| 32 |
+
n_iterations: number;
|
| 33 |
+
summary_stats: SummaryStats;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// Full iteration detail
|
| 37 |
+
export interface IterationDetail {
|
| 38 |
+
index: number;
|
| 39 |
+
iteration: number;
|
| 40 |
+
island_id: number;
|
| 41 |
+
score: number;
|
| 42 |
+
best_score: number;
|
| 43 |
+
delta: number;
|
| 44 |
+
adaptation_type: string;
|
| 45 |
+
exploration_intensity: number;
|
| 46 |
+
is_valid: boolean;
|
| 47 |
+
task_id: string;
|
| 48 |
+
prompt_text: string;
|
| 49 |
+
reasoning_trace: string;
|
| 50 |
+
program_code: string;
|
| 51 |
+
meta_guidance_tactic: string;
|
| 52 |
+
tactic_approach_type: string;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// Preset
|
| 56 |
+
export interface Preset {
|
| 57 |
+
id: string;
|
| 58 |
+
name: string;
|
| 59 |
+
repo: string;
|
| 60 |
+
split: string;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// View mode
|
| 64 |
+
export type ViewMode = "timeline" | "detail";
|