import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"; import { useEffect } from "react"; import { useCommandPaletteStore } from "../commandPaletteStore"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { startNewLocalThreadFromContext, startNewThreadFromContext, } from "../lib/chatThreadActions"; import { isTerminalFocused } from "../lib/terminalFocus"; import { resolveShortcutCommand } from "../keybindings"; import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore"; import { useThreadSelectionStore } from "../threadSelectionStore"; import { resolveSidebarNewThreadEnvMode } from "~/components/Sidebar.logic"; import { useSettings } from "~/hooks/useSettings"; import { useServerKeybindings } from "~/rpc/serverState"; function ChatRouteGlobalShortcuts() { const clearSelection = useThreadSelectionStore((state) => state.clearSelection); const selectedThreadKeysSize = useThreadSelectionStore((state) => state.selectedThreadKeys.size); const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread, routeThreadRef } = useHandleNewThread(); const keybindings = useServerKeybindings(); const terminalOpen = useTerminalUiStateStore((state) => routeThreadRef ? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen : false, ); const appSettings = useSettings(); useEffect(() => { const onWindowKeyDown = (event: KeyboardEvent) => { if (event.defaultPrevented) return; const command = resolveShortcutCommand(event, keybindings, { context: { terminalFocus: isTerminalFocused(), terminalOpen, }, }); if (useCommandPaletteStore.getState().open) { return; } if (event.key === "Escape" && selectedThreadKeysSize > 0) { event.preventDefault(); clearSelection(); return; } if (command === "chat.newLocal") { event.preventDefault(); event.stopPropagation(); void startNewLocalThreadFromContext({ activeDraftThread, activeThread, defaultProjectRef, defaultThreadEnvMode: resolveSidebarNewThreadEnvMode({ defaultEnvMode: appSettings.defaultThreadEnvMode, }), handleNewThread, }); return; } if (command === "chat.new") { event.preventDefault(); event.stopPropagation(); void startNewThreadFromContext({ activeDraftThread, activeThread, defaultProjectRef, defaultThreadEnvMode: resolveSidebarNewThreadEnvMode({ defaultEnvMode: appSettings.defaultThreadEnvMode, }), handleNewThread, }); } }; window.addEventListener("keydown", onWindowKeyDown); return () => { window.removeEventListener("keydown", onWindowKeyDown); }; }, [ activeDraftThread, activeThread, clearSelection, handleNewThread, keybindings, defaultProjectRef, selectedThreadKeysSize, terminalOpen, appSettings.defaultThreadEnvMode, ]); return null; } function ChatRouteLayout() { return ( <> ); } export const Route = createFileRoute("/_chat")({ beforeLoad: async ({ context }) => { if ( context.authGateState.status !== "authenticated" && context.authGateState.status !== "hosted-static" ) { throw redirect({ to: "/pair", replace: true }); } }, component: ChatRouteLayout, });