111 lines
4.4 KiB
JavaScript
111 lines
4.4 KiB
JavaScript
class ThinkingPanel {
|
|
constructor(thinkingEl) {
|
|
this._el = thinkingEl;
|
|
}
|
|
|
|
get element() { return this._el; }
|
|
|
|
render(turn, replay) {
|
|
if (!turn) {
|
|
this._el.innerHTML = "<p class=\"section-title\">Select a game to inspect reasoning.</p>";
|
|
this._syncMonoOffset();
|
|
return;
|
|
}
|
|
|
|
const reasoning = turn.my_thinking;
|
|
const reasons = this._extractReasoningList(reasoning);
|
|
const reasonList = reasons.map((item) => `<li>${item}</li>`).join("");
|
|
const gameMeta = replay && replay.game ? replay.game : {};
|
|
const snakeType = Utils.safeString(gameMeta.your_snake_type);
|
|
const snakeVersion = Utils.safeString(gameMeta.your_snake_version);
|
|
|
|
this._el.innerHTML = `
|
|
<div class="think-grid">
|
|
<div class="chip"><span class="k">Chosen Move</span><span class="v">${Utils.safeString(turn.my_move)}</span></div>
|
|
<div class="chip"><span class="k">Snake Type</span><span class="v">${snakeType}</span></div>
|
|
<div class="chip"><span class="k">Snake Version</span><span class="v">${snakeVersion}</span></div>
|
|
<div class="chip"><span class="k">Observed At</span><span class="v">${Utils.formatObservedAtLocal(turn.observed_at)}</span></div>
|
|
<div class="chip"><span class="k">Food Count</span><span class="v">${Array.isArray(turn.food) ? turn.food.length : 0}</span></div>
|
|
<div class="chip"><span class="k">Hazard Count</span><span class="v">${Array.isArray(turn.hazards) ? turn.hazards.length : 0}</span></div>
|
|
</div>
|
|
|
|
<section class="snakes-section">
|
|
<table class="score-table">
|
|
<colgroup>
|
|
<col style="width:32%">
|
|
<col style="width:10%">
|
|
<col style="width:10%">
|
|
<col style="width:20%">
|
|
<col style="width:6%">
|
|
<col style="width:7%">
|
|
<col style="width:7%">
|
|
</colgroup>
|
|
<thead><tr><th>Snake</th><th>Latency</th><th>Move</th><th>Health</th><th>Length</th><th>Head</th><th>Tail</th></tr></thead>
|
|
<tbody>${SnakeTable.buildSnakesRows(turn, replay)}</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section class="scores-section">
|
|
<p class="section-title">Move Scores</p>
|
|
<table class="score-table">
|
|
<colgroup><col style="width:50%"><col style="width:50%"></colgroup>
|
|
<thead><tr><th>Move</th><th>Score</th></tr></thead>
|
|
<tbody>${MoveTable.buildScoresRows(reasoning)}</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section>
|
|
<p class="section-title">Decision Summary</p>
|
|
<ul class="reason-list">${reasonList}</ul>
|
|
</section>
|
|
|
|
<section class="raw-block">
|
|
<p class="section-title">Raw Reasoning Payload</p>
|
|
<pre class="mono">${JSON.stringify(reasoning, null, 2)}</pre>
|
|
</section>
|
|
`;
|
|
this._syncMonoOffset();
|
|
}
|
|
|
|
highlightSnake(snakeId) {
|
|
const section = this._el.querySelector(".snakes-section");
|
|
if (!section) return;
|
|
section.querySelectorAll(".snake-row.highlighted").forEach((r) => r.classList.remove("highlighted"));
|
|
section.classList.remove("has-highlight");
|
|
if (!snakeId) return;
|
|
const row = section.querySelector(`[data-snake-id="${CSS.escape(snakeId)}"]`);
|
|
if (row) {
|
|
row.classList.add("highlighted");
|
|
section.classList.add("has-highlight");
|
|
}
|
|
}
|
|
|
|
syncMonoOffset() {
|
|
this._syncMonoOffset();
|
|
}
|
|
|
|
_syncMonoOffset() {
|
|
const mono = this._el.querySelector(".mono");
|
|
if (!mono) return;
|
|
const rect = mono.getBoundingClientRect();
|
|
const bottomPaddingPx = 36;
|
|
const offset = Math.max(120, Math.round(rect.top + bottomPaddingPx));
|
|
document.documentElement.style.setProperty("--mono-vh-offset", `${offset}px`);
|
|
}
|
|
|
|
_extractReasoningList(reasoning) {
|
|
const parts = [];
|
|
if (!reasoning || typeof reasoning !== "object") {
|
|
return ["No reasoning recorded by this snake implementation."];
|
|
}
|
|
if (reasoning.reason) parts.push(`Reason: ${reasoning.reason}`);
|
|
if (reasoning.mode) parts.push(`Mode: ${reasoning.mode}`);
|
|
if (reasoning.health !== undefined) parts.push(`Health: ${reasoning.health}`);
|
|
if (reasoning.length !== undefined) parts.push(`Length: ${reasoning.length}`);
|
|
if (reasoning.occupancy !== undefined) parts.push(`Occupancy: ${reasoning.occupancy}`);
|
|
if (reasoning.ms_remaining !== undefined) parts.push(`Time left: ${reasoning.ms_remaining}ms`);
|
|
if (parts.length === 0) parts.push("Structured reasoning not provided; showing raw payload below.");
|
|
return parts;
|
|
}
|
|
}
|