- add `serverConfigQueryOptions` in new `serverReactQuery` utility - switch `ChatView` from manual `getConfig` effect/state to `useQuery` - default to empty keybindings while config is unavailable
Unified · Split
| @@ -7,6 +7,7 @@ import { · 6 unmodified lines | ||
| 7 | 7 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, |
| 8 | 8 | type ProviderApprovalDecision, |
| 9 | 9 | type ProviderSendTurnAttachmentInput, |
| 10 | + type KeybindingsConfig, | |
| 10 | 11 | } from "@t3tools/contracts"; |
| 11 | 12 | import { |
| 12 | 13 | type ClipboardEvent, |
| @@ -26,6 +27,7 @@ import { · 13 unmodified lines | ||
| 26 | 27 | gitBranchesQueryOptions, |
| 27 | 28 | gitCreateWorktreeMutationOptions, |
| 28 | 29 | } from "~/lib/gitReactQuery"; |
| 30 | +import { serverConfigQueryOptions } from "~/lib/serverReactQuery"; | |
| 29 | 31 | |
| 30 | 32 | import { isElectron } from "../env"; |
| 31 | 33 | import { buildBootstrapInput } from "../historyBootstrap"; |
| @@ -55,7 +57,6 @@ import { · 23 unmodified lines | ||
| 55 | 57 | isTerminalNewShortcut, |
| 56 | 58 | isTerminalSplitShortcut, |
| 57 | 59 | isTerminalToggleShortcut, |
| 58 | − type ResolvedTerminalKeybindings, | |
| 59 | 60 | shortcutLabelForCommand, |
| 60 | 61 | } from "../keybindings"; |
| 61 | 62 | import ChatMarkdown from "./ChatMarkdown"; |
| @@ -90,6 +91,7 @@ const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; · 28 unmodified lines | ||
| 90 | 91 | const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`; |
| 91 | 92 | const IMAGE_ONLY_BOOTSTRAP_PROMPT = |
| 92 | 93 | "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]"; |
| 94 | +const EMPTY_KEYBINDINGS: KeybindingsConfig = []; | |
| 93 | 95 | |
| 94 | 96 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| 95 | 97 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; |
| @@ -151,7 +153,6 @@ export default function ChatView() { · 55 unmodified lines | ||
| 151 | 153 | const [expandedWorkGroups, setExpandedWorkGroups] = useState<Record<string, boolean>>({}); |
| 152 | 154 | const [nowTick, setNowTick] = useState(() => Date.now()); |
| 153 | 155 | const [terminalFocusRequestId, setTerminalFocusRequestId] = useState(0); |
| 154 | − const [terminalKeybindings, setTerminalKeybindings] = useState<ResolvedTerminalKeybindings>([]); | |
| 155 | 156 | const messagesScrollRef = useRef<HTMLDivElement>(null); |
| 156 | 157 | const messagesEndRef = useRef<HTMLDivElement>(null); |
| 157 | 158 | const shouldAutoScrollRef = useRef(true); |
| @@ -262,6 +263,11 @@ export default function ChatView() { · 104 unmodified lines | ||
| 262 | 263 | } as const); |
| 263 | 264 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; |
| 264 | 265 | const branchesQuery = useQuery(gitBranchesQueryOptions(api, gitCwd)); |
| 266 | + const terminalKeybindingsQuery = useQuery({ | |
| 267 | + ...serverConfigQueryOptions(api), | |
| 268 | + select: (config) => config.keybindings, | |
| 269 | + }); | |
| 270 | + const terminalKeybindings = terminalKeybindingsQuery.data ?? EMPTY_KEYBINDINGS; | |
| 265 | 271 | // Default true while loading to avoid toolbar flicker. |
| 266 | 272 | const isGitRepo = branchesQuery.data?.isRepo ?? true; |
| 267 | 273 | const splitTerminalShortcutLabel = useMemo( |
| @@ -279,30 +285,6 @@ export default function ChatView() { · 11 unmodified lines | ||
| 279 | 285 | (activeThread.session !== null && activeThread.session.status !== "closed")), |
| 280 | 286 | ); |
| 281 | 287 | |
| 282 | − useEffect(() => { | |
| 283 | − if (!api || !api.server || typeof api.server.getConfig !== "function") { | |
| 284 | − setTerminalKeybindings([]); | |
| 285 | − return; | |
| 286 | − } | |
| 287 | − | |
| 288 | − let disposed = false; | |
| 289 | − | |
| 290 | − void api.server | |
| 291 | − .getConfig() | |
| 292 | − .then((config) => { | |
| 293 | − if (disposed) return; | |
| 294 | − setTerminalKeybindings(config.keybindings); | |
| 295 | − }) | |
| 296 | − .catch(() => { | |
| 297 | − if (disposed) return; | |
| 298 | − setTerminalKeybindings([]); | |
| 299 | − }); | |
| 300 | − | |
| 301 | − return () => { | |
| 302 | − disposed = true; | |
| 303 | − }; | |
| 304 | − }, [api]); | |
| 305 | − | |
| 306 | 288 | const revokePreviewUrls = useCallback((images: Array<{ previewUrl?: string }>) => { |
| 307 | 289 | for (const image of images) { |
| 308 | 290 | if (!image.previewUrl) continue; |
| @@ -0,0 +1,20 @@ | ||
| 1 | +import type { NativeApi } from "@t3tools/contracts"; | |
| 2 | +import { queryOptions } from "@tanstack/react-query"; | |
| 3 | + | |
| 4 | +export const serverQueryKeys = { | |
| 5 | + all: ["server"] as const, | |
| 6 | + config: () => ["server", "config"] as const, | |
| 7 | +}; | |
| 8 | + | |
| 9 | +export function serverConfigQueryOptions(api: NativeApi | undefined) { | |
| 10 | + return queryOptions({ | |
| 11 | + queryKey: serverQueryKeys.config(), | |
| 12 | + queryFn: async () => { | |
| 13 | + if (!api || !api.server || typeof api.server.getConfig !== "function") { | |
| 14 | + throw new Error("Server config is unavailable."); | |
| 15 | + } | |
| 16 | + return api.server.getConfig(); | |
| 17 | + }, | |
| 18 | + enabled: !!api && !!api.server && typeof api.server.getConfig === "function", | |
| 19 | + }); | |
| 20 | +} | |