@@ -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
168194def 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-
597589def tab_select (evt : gr .SelectData ):
598590 global info
599591 info .active_tab = evt .value
0 commit comments