Skip to content

Commit 54a729c

Browse files
fix: keyboard navigation shortcut in xray
1 parent f4c3fea commit 54a729c

File tree

1 file changed

+56
-64
lines changed

1 file changed

+56
-64
lines changed

src/agentlab/analyze/agent_xray.py

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@ def filter_agent_id(self, agent_id: list[tuple]):
164164
}
165165
"""
166166

167+
# Keyboard shortcut JavaScript - based on https://github.com/gradio-app/gradio/issues/6101
168+
shortcut_js = """
169+
<script>
170+
function shortcuts(e) {
171+
var event = document.all ? window.event : e;
172+
switch (e.target.tagName.toLowerCase()) {
173+
case "input":
174+
case "textarea":
175+
case "select":
176+
case "button":
177+
return;
178+
default:
179+
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && (e.metaKey || e.ctrlKey)) {
180+
e.preventDefault();
181+
if (e.key === 'ArrowLeft') {
182+
document.getElementById("prev_btn").click();
183+
} else {
184+
document.getElementById("next_btn").click();
185+
}
186+
}
187+
}
188+
}
189+
document.addEventListener('keydown', shortcuts, false);
190+
</script>
191+
"""
192+
167193

168194
def run_gradio(results_dir: Path):
169195
"""
@@ -173,14 +199,12 @@ def run_gradio(results_dir: Path):
173199
global info
174200
info.results_dir = results_dir
175201

176-
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
202+
with gr.Blocks(theme=gr.themes.Soft(), css=css, head=shortcut_js) as demo:
177203
agent_id = gr.State(value=None)
178204
episode_id = gr.State(value=EpisodeId())
179205
agent_task_id = gr.State(value=None)
180206
step_id = gr.State(value=None)
181207

182-
hidden_key_input = gr.Textbox(visible=False, elem_id="key_capture")
183-
184208
with gr.Accordion("Help", open=False):
185209
gr.Markdown(
186210
"""\
@@ -302,10 +326,15 @@ def run_gradio(results_dir: Path):
302326
action_info = gr.Markdown(label="Action Info", elem_classes="my-markdown")
303327
state_error = gr.Markdown(label="Next Step Error", elem_classes="my-markdown")
304328

305-
with gr.Row(variant="panel"):
306-
prev_btn = gr.Button("◀ Previous", size="md", scale=0)
307-
next_btn = gr.Button("Next ▶", size="md", scale=0)
308-
step_indicator = gr.Markdown("**Step 0/0**")
329+
with gr.Row(variant="panel", elem_classes=["items-center", "justify-center"]):
330+
step_indicator = gr.Markdown("### Step 0/0", elem_classes=["text-center"])
331+
prev_btn = gr.Button(
332+
"◀ Previous", size="md", scale=0, elem_id="prev_btn", elem_classes=["mx-auto"]
333+
)
334+
next_btn = gr.Button(
335+
"Next ▶", size="md", scale=0, elem_id="next_btn", elem_classes=["mx-auto"]
336+
)
337+
gr.Markdown("(Shortcut: Ctrl/Cmd + ← →)", elem_classes=["text-center"])
309338

310339
profiling_gr = gr.Image(
311340
label="Profiling", show_label=False, interactive=False, show_download_button=False
@@ -516,53 +545,35 @@ def run_gradio(results_dir: Path):
516545

517546
demo.load(fn=refresh_exp_dir_choices, inputs=exp_dir_choice, outputs=exp_dir_choice)
518547

519-
demo.load(
520-
None,
521-
None,
522-
None,
523-
js="""
524-
function() {
525-
document.addEventListener('keydown', function(e) {
526-
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && (e.metaKey || e.ctrlKey)) {
527-
e.preventDefault();
528-
const hiddenInput = document.querySelector('#key_capture input, #key_capture textarea');
529-
if (hiddenInput) {
530-
let event = e.key === 'ArrowLeft' ? 'Cmd+Left' : 'Cmd+Right';
531-
hiddenInput.value = event;
532-
hiddenInput.dispatchEvent(new Event('input', {bubbles: true}));
533-
}
534-
}
535-
});
536-
}
537-
""",
538-
)
539-
hidden_key_input.change(
540-
handle_key_event,
541-
inputs=[hidden_key_input, step_id],
542-
outputs=[hidden_key_input, step_id],
543-
)
548+
# Simple navigation button events
549+
def navigate_prev(step_id: StepId):
550+
global info
551+
if step_id and step_id.step is not None and step_id.episode_id:
552+
step = max(0, step_id.step - 1)
553+
info.step = step
554+
return StepId(episode_id=step_id.episode_id, step=step)
555+
return step_id
544556

545-
# Simple navigation button events - reuse keyboard logic
546-
prev_btn.click(
547-
lambda step_id: handle_key_event("Cmd+Left", step_id)[1],
548-
inputs=[step_id],
549-
outputs=[step_id],
550-
)
551-
next_btn.click(
552-
lambda step_id: handle_key_event("Cmd+Right", step_id)[1],
553-
inputs=[step_id],
554-
outputs=[step_id],
555-
)
557+
def navigate_next(step_id: StepId):
558+
global info
559+
if step_id and step_id.step is not None and step_id.episode_id and info.exp_result:
560+
step = min(len(info.exp_result.steps_info) - 1, step_id.step + 1)
561+
info.step = step
562+
return StepId(episode_id=step_id.episode_id, step=step)
563+
return step_id
564+
565+
prev_btn.click(navigate_prev, inputs=[step_id], outputs=[step_id])
566+
next_btn.click(navigate_next, inputs=[step_id], outputs=[step_id])
556567

557568
# Update step indicator display
558569
def format_step_indicator(step_id):
559570
global info
560571
if not step_id or not info.exp_result or not info.exp_result.steps_info:
561-
return "Step 0/0"
572+
return "### Step 0/0"
562573
# 1-based for user, total steps is len-1 (last is terminal)
563574
current = (step_id.step + 1) if step_id.step is not None else 0
564575
total = max(len(info.exp_result.steps_info) - 1, 0)
565-
return f"Step {current}/{total}"
576+
return f"### Step {current}/{total}"
566577

567578
step_id.change(format_step_indicator, inputs=[step_id], outputs=[step_indicator])
568579

@@ -575,25 +586,6 @@ def format_step_indicator(step_id):
575586
demo.launch(server_port=port, share=do_share)
576587

577588

578-
def handle_key_event(key_event, step_id: StepId):
579-
580-
if key_event:
581-
global info
582-
583-
# print(f"Key event: {key_event}")
584-
step = step_id.step
585-
if key_event.startswith("Cmd+Left"):
586-
step = max(0, step - 1)
587-
elif key_event.startswith("Cmd+Right"):
588-
step = min(len(info.exp_result.steps_info) - 2, step + 1)
589-
else:
590-
return gr.update()
591-
# print(f"Updating step to {step} from key event {key_event}")
592-
info.step = step
593-
step_id = StepId(episode_id=step_id.episode_id, step=step)
594-
return ("", step_id)
595-
596-
597589
def tab_select(evt: gr.SelectData):
598590
global info
599591
info.active_tab = evt.value

0 commit comments

Comments
 (0)