Skip to content

Commit 8b08c1f

Browse files
fix: keyboard navigation shortcut in xray
1 parent 08f4d40 commit 8b08c1f

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
@@ -160,6 +160,32 @@ def filter_agent_id(self, agent_id: list[tuple]):
160160
}
161161
"""
162162

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

164190
def run_gradio(results_dir: Path):
165191
"""
@@ -169,14 +195,12 @@ def run_gradio(results_dir: Path):
169195
global info
170196
info.results_dir = results_dir
171197

172-
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
198+
with gr.Blocks(theme=gr.themes.Soft(), css=css, head=shortcut_js) as demo:
173199
agent_id = gr.State(value=None)
174200
episode_id = gr.State(value=EpisodeId())
175201
agent_task_id = gr.State(value=None)
176202
step_id = gr.State(value=None)
177203

178-
hidden_key_input = gr.Textbox(visible=False, elem_id="key_capture")
179-
180204
with gr.Accordion("Help", open=False):
181205
gr.Markdown(
182206
"""\
@@ -298,10 +322,15 @@ def run_gradio(results_dir: Path):
298322
action_info = gr.Markdown(label="Action Info", elem_classes="my-markdown")
299323
state_error = gr.Markdown(label="Next Step Error", elem_classes="my-markdown")
300324

301-
with gr.Row(variant="panel"):
302-
prev_btn = gr.Button("◀ Previous", size="md", scale=0)
303-
next_btn = gr.Button("Next ▶", size="md", scale=0)
304-
step_indicator = gr.Markdown("**Step 0/0**")
325+
with gr.Row(variant="panel", elem_classes=["items-center", "justify-center"]):
326+
step_indicator = gr.Markdown("### Step 0/0", elem_classes=["text-center"])
327+
prev_btn = gr.Button(
328+
"◀ Previous", size="md", scale=0, elem_id="prev_btn", elem_classes=["mx-auto"]
329+
)
330+
next_btn = gr.Button(
331+
"Next ▶", size="md", scale=0, elem_id="next_btn", elem_classes=["mx-auto"]
332+
)
333+
gr.Markdown("(Shortcut: Ctrl/Cmd + ← →)", elem_classes=["text-center"])
305334

306335
profiling_gr = gr.Image(
307336
label="Profiling", show_label=False, interactive=False, show_download_button=False
@@ -512,53 +541,35 @@ def run_gradio(results_dir: Path):
512541

513542
demo.load(fn=refresh_exp_dir_choices, inputs=exp_dir_choice, outputs=exp_dir_choice)
514543

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

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

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

563574
step_id.change(format_step_indicator, inputs=[step_id], outputs=[step_indicator])
564575

@@ -571,25 +582,6 @@ def format_step_indicator(step_id):
571582
demo.launch(server_port=port, share=do_share)
572583

573584

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

0 commit comments

Comments
 (0)