Skip to content

Commit 4d4f146

Browse files
committed
Add playground failure timeline
1 parent 2fbc9bb commit 4d4f146

5 files changed

Lines changed: 107 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ python3 examples/runtime/01_sense_act_loop.py
5252
| Failure recovery | `python3 examples/manipulation/01_pick_and_retry.py` | grasp miss -> belief update -> retry |
5353
| Runtime safety | `python3 examples/navigation/29_safety_filter_cbf.py` | nominal controller -> CBF projection -> safe motion |
5454
| Active perception | `python3 examples/navigation/07_active_slam_toy.py` | map and pose uncertainty -> information-seeking action |
55-
| Shareable live trace | [Try live](https://rsasaki0109.github.io/PythonInteractiveRobotics/playground.html?scenario=household&answer=red&compare=1&autoplay=1) | compare, step, and scrub action, reward, failure, and agent_state |
55+
| Shareable live trace | [Try live](https://rsasaki0109.github.io/PythonInteractiveRobotics/playground.html?scenario=household&answer=red&compare=1&autoplay=1) | compare, scrub, and jump through failure timelines |
5656
| Human correction | [Open in Colab](https://colab.research.google.com/github/rsasaki0109/PythonInteractiveRobotics/blob/main/notebooks/human_correction_replanning.ipynb) | shortcut -> human correction -> cost update -> replan |
5757
| Language ambiguity | [Open in Colab](https://colab.research.google.com/github/rsasaki0109/PythonInteractiveRobotics/blob/main/notebooks/clarifying_question.ipynb) | ambiguous command -> ask question -> answer -> act |
5858
| Integrated household task | [Open in Colab](https://colab.research.google.com/github/rsasaki0109/PythonInteractiveRobotics/blob/main/notebooks/household_task_agent.ipynb) | clarify -> plan -> safety check -> retry -> human replan |

docs/playground.css

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ button:disabled {
497497

498498
.trace-panel {
499499
display: grid;
500-
grid-template-rows: auto minmax(0, 1fr);
500+
grid-template-rows: auto auto minmax(0, 1fr);
501501
max-height: calc(100vh - 124px);
502502
padding: 16px;
503503
}
@@ -520,6 +520,67 @@ button:disabled {
520520
font-weight: 800;
521521
}
522522

523+
.timeline {
524+
align-content: start;
525+
border: 1px solid var(--line);
526+
border-radius: 8px;
527+
display: grid;
528+
gap: 6px;
529+
grid-template-columns: repeat(auto-fill, minmax(28px, 1fr));
530+
margin-bottom: 12px;
531+
min-height: 40px;
532+
padding: 8px;
533+
}
534+
535+
.timeline-step {
536+
align-items: center;
537+
background: #eef3ef;
538+
border: 1px solid var(--line);
539+
border-radius: 999px;
540+
color: var(--ink);
541+
cursor: pointer;
542+
display: flex;
543+
font-size: 0.72rem;
544+
font-weight: 900;
545+
height: 28px;
546+
justify-content: center;
547+
min-height: 28px;
548+
min-width: 0;
549+
padding: 0;
550+
}
551+
552+
.timeline-step:hover {
553+
transform: translateY(-1px);
554+
}
555+
556+
.timeline-active {
557+
box-shadow: 0 0 0 3px rgba(23, 32, 31, 0.18);
558+
}
559+
560+
.timeline-ambiguous {
561+
background: #805ad5;
562+
border-color: #805ad5;
563+
color: #ffffff;
564+
}
565+
566+
.timeline-unsafe {
567+
background: #f4ce82;
568+
border-color: #b7791f;
569+
}
570+
571+
.timeline-grasp,
572+
.timeline-human {
573+
background: #c43d32;
574+
border-color: #c43d32;
575+
color: #ffffff;
576+
}
577+
578+
.timeline-success {
579+
background: #5c8b4a;
580+
border-color: #5c8b4a;
581+
color: #ffffff;
582+
}
583+
523584
.trace-table {
524585
border: 1px solid var(--line);
525586
border-radius: 8px;

docs/playground.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ <h1 id="playground-title">Playground</h1>
112112
<h2 id="trace-title">Trace</h2>
113113
<span id="stepCounter">0 / 3</span>
114114
</div>
115+
<div id="timeline" class="timeline" aria-label="Failure timeline"></div>
115116
<div class="trace-table" role="table" aria-label="Trace rows">
116117
<div class="trace-row trace-head" role="row">
117118
<span role="columnheader">#</span>

docs/playground.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
replay: document.getElementById("replaySlider"),
5555
replayValue: document.getElementById("replayValue"),
5656
comparePanel: document.getElementById("comparePanel"),
57+
timeline: document.getElementById("timeline"),
5758
scene: document.getElementById("scene"),
5859
traceRows: document.getElementById("traceRows"),
5960
stepCounter: document.getElementById("stepCounter"),
@@ -575,6 +576,7 @@
575576

576577
renderReplay(replayIndex);
577578
renderCompare();
579+
renderTimeline(replayIndex);
578580
renderScene(current);
579581
renderTrace(replayIndex);
580582
}
@@ -632,6 +634,42 @@
632634
elements.replayValue.textContent = replayLabel(replayIndex);
633635
}
634636

637+
function renderTimeline(replayIndex) {
638+
elements.timeline.textContent = "";
639+
state.trace.forEach((event, index) => {
640+
const step = index + 1;
641+
const button = document.createElement("button");
642+
button.className = "timeline-step " + timelineClass(event);
643+
button.type = "button";
644+
button.textContent = String(step);
645+
button.setAttribute("aria-label", timelineLabel(event, step));
646+
if (step === replayIndex) {
647+
button.classList.add("timeline-active");
648+
button.setAttribute("aria-current", "step");
649+
}
650+
button.addEventListener("click", () => setReplayIndex(step));
651+
elements.timeline.appendChild(button);
652+
});
653+
}
654+
655+
function timelineClass(event) {
656+
if (event.failure === "ambiguous_goal") return "timeline-ambiguous";
657+
if (event.failure === "unsafe_nominal_step") return "timeline-unsafe";
658+
if (event.failure === "grasp_miss") return "timeline-grasp";
659+
if (event.failure === "human_correction") return "timeline-human";
660+
if (event.agentState === "done") return "timeline-success";
661+
return "timeline-neutral";
662+
}
663+
664+
function timelineLabel(event, step) {
665+
return (
666+
"Step " +
667+
step +
668+
": " +
669+
(event.failure || (event.agentState === "done" ? "success" : event.agentState))
670+
);
671+
}
672+
635673
function currentReplayIndex() {
636674
if (state.replayIndex === null) {
637675
return state.trace.length;

tests/test_playground_assets.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def test_playground_assets_support_shareable_scenarios() -> None:
2525
"replaySlider",
2626
"replayValue",
2727
"comparePanel",
28+
"timeline",
2829
"traceRows",
2930
):
3031
assert f'id="{element_id}"' in html
@@ -42,9 +43,12 @@ def test_playground_assets_support_shareable_scenarios() -> None:
4243
"buildHouseholdCompare",
4344
"renderCompare",
4445
"renderMiniMap",
46+
"renderTimeline",
47+
"timelineClass",
4548
"clampReplayIndex",
4649
"snapshotForReplayIndex",
4750
"trace-active",
51+
"timeline-active",
4852
):
4953
assert value in js
5054

@@ -54,6 +58,7 @@ def test_playground_assets_support_shareable_scenarios() -> None:
5458
assert ".copy-status" in css
5559
assert ".replay-strip" in css
5660
assert ".compare-panel" in css
61+
assert ".timeline" in css
5762

5863

5964
def test_readme_links_to_shareable_live_trace() -> None:

0 commit comments

Comments
 (0)