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 files

New 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 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
+ &larr; 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
+ &larr; 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 &rarr;
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";