Skip to content

Commit 8631282

Browse files
authored
Fix env.render(mode="ipython") in Jupyter after Vite refactor (#1305)
PR #1152 wired vite-built visualizers into html_renderer() but the notebook render path still didn't work — env.render(mode="ipython") produced a blank iframe. Two things were broken: 1. get_player() in kaggle_environments/utils.py treated the full HTML document returned by html_renderer() as a JS snippet and spliced it into static/player.html via renderer.strip(), producing a garbage page. Add a third branch that detects a full HTML document (leading "<") and injects `<script>window.kaggle = {...};</script>` before </head> instead. 2. web/core/src/player/player.ts::loadData() only accepted initial replay data from HMR state, VITE_REPLAY_FILE, or postMessage. A notebook iframe uses srcdoc with no parent script, so nothing ever posted → visualizer sat at "Loading..." forever. Add a window.kaggle initial-data source alongside the existing tiers, reusing the same agent-derivation as the VITE_REPLAY_FILE branch. The static/player.html fallback path is untouched — envs that still ship a single-file JS renderer (orbit_wars) continue to work exactly as before. GCS-served visualizers are unaffected: window.kaggle is only injected in the Python get_player() call path. Verified end-to-end: rebuilt connectx, rendered via env.render(mode="html"), embedded via srcdoc in a browser — the board renders, the yellow chip appears on step 2, playback controls populate 1/10 steps. Bump version to 1.31.0.
1 parent 8c8ace4 commit 8631282

3 files changed

Lines changed: 36 additions & 2 deletions

File tree

kaggle_environments/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,22 @@ def get_player(window_kaggle: dict[str, Any], renderer: tuple[str, str] | str) -
241241
"""
242242
return read_file(renderer[1]).replace(key, value)
243243

244+
# Vite-built visualizers return a self-contained HTML document. Inject the
245+
# window.kaggle payload directly so the visualizer picks it up on load —
246+
# notebook iframes are srcdoc'd with no parent to postMessage from.
247+
if isinstance(renderer, str) and renderer.lstrip().startswith("<"):
248+
snippet = f"<script>window.kaggle = {json.dumps(window_kaggle)};</script>"
249+
lower = renderer.lower()
250+
head_close = lower.find("</head>")
251+
if head_close != -1:
252+
return renderer[:head_close] + snippet + renderer[head_close:]
253+
body_open = lower.find("<body")
254+
if body_open != -1:
255+
body_end = renderer.find(">", body_open)
256+
if body_end != -1:
257+
return renderer[: body_end + 1] + snippet + renderer[body_end + 1 :]
258+
return snippet + renderer
259+
244260
key = "/*window.kaggle*/"
245261
value = f"""
246262
window.kaggle = {json.dumps(window_kaggle, indent=2)};\n\n

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "kaggle-environments"
3-
version = "1.30.2"
3+
version = "1.31.0"
44
description = "Kaggle Environments"
55
authors = [
66
{name = "Kaggle", email = "support@kaggle.com"},

web/core/src/player/player.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,26 @@ export class ReplayVisualizer<TSteps extends BaseGameStep[] = BaseGameStep[]> {
9191
}
9292

9393
// 3. (PRIORITY 3) Production build (or not DEV) and no HMR data.
94+
// Check for a Python-injected window.kaggle payload (notebook iframe
95+
// srcdoc path from kaggle_environments.utils.get_player).
9496
else {
95-
this.viewer.innerHTML = '<div>Loading...</div>';
97+
const initial = (window as unknown as { kaggle?: any }).kaggle;
98+
if (initial?.environment) {
99+
const data = initial.environment;
100+
let agents = data.info?.Agents;
101+
if (!agents && data.steps?.[0]) {
102+
const playerCount = Array.isArray(data.steps[0]) ? data.steps[0].length : 0;
103+
const teamNames = data.info?.TeamNames || [];
104+
agents = Array.from({ length: playerCount }, (_, i) => ({
105+
index: i,
106+
name: teamNames[i] || `Player ${i + 1}`,
107+
}));
108+
}
109+
if (typeof initial.step === 'number') this.step = initial.step;
110+
this.setData(data, agents);
111+
} else {
112+
this.viewer.innerHTML = '<div>Loading...</div>';
113+
}
96114
}
97115

98116
// 4. Add listener (always)

0 commit comments

Comments
 (0)