From fb9b80ac4800a1ea5a64927b0ef268da29cf86a6 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Wed, 30 Sep 2026 00:40:12 -0700 Subject: [PATCH] feat(ui): add trace keyboard navigation --- .../view_logs/TraceView/useTraceNavigation.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/useTraceNavigation.ts diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/useTraceNavigation.ts b/ui/litellm-dashboard/src/components/view_logs/TraceView/useTraceNavigation.ts new file mode 100644 index 00000000000..7910a306aaa --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/useTraceNavigation.ts @@ -0,0 +1,33 @@ +import { useEffect } from "react"; + +import { KEY_J_LOWER, KEY_J_UPPER, KEY_K_LOWER, KEY_K_UPPER } from "../LogDetailsDrawer/constants"; + +const isTyping = (target: EventTarget | null): boolean => + target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement; + +/** Id `delta` steps away from `current` in `order`, clamped to the ends. */ +export const stepSelection = (order: readonly string[], current: string | null, delta: number): string | null => { + if (order.length === 0) return null; + const index = current ? order.indexOf(current) : -1; + if (index < 0) return order[0]; + return order[Math.min(Math.max(index + delta, 0), order.length - 1)]; +}; + +/** J / ArrowDown selects the next span, K / ArrowUp the previous one. Esc is handled by the Sheet. */ +export function useTraceNavigation(active: boolean, onStep: (delta: number) => void): void { + useEffect(() => { + if (!active) return; + const handleKeyDown = (event: KeyboardEvent) => { + if (isTyping(event.target)) return; + if ([KEY_J_LOWER, KEY_J_UPPER, "ArrowDown"].includes(event.key)) { + event.preventDefault(); + onStep(1); + } else if ([KEY_K_LOWER, KEY_K_UPPER, "ArrowUp"].includes(event.key)) { + event.preventDefault(); + onStep(-1); + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [active, onStep]); +}