@@ -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
164190def 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-
593585def tab_select (evt : gr .SelectData ):
594586 global info
595587 info .active_tab = evt .value
0 commit comments