| ... | @@ -7,6 +7,7 @@ import { | ... | @@ -7,6 +7,7 @@ import { |
| 7 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, | 7 | PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, |
| 8 | type ProviderApprovalDecision, | 8 | type ProviderApprovalDecision, |
| 9 | type ProviderSendTurnAttachmentInput, | 9 | type ProviderSendTurnAttachmentInput, |
| | 10 | type KeybindingsConfig, |
| 10 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 11 | import { | 12 | import { |
| 12 | type ClipboardEvent, | 13 | type ClipboardEvent, |
| ... | @@ -26,6 +27,7 @@ import { | ... | @@ -26,6 +27,7 @@ import { |
| 26 | gitBranchesQueryOptions, | 27 | gitBranchesQueryOptions, |
| 27 | gitCreateWorktreeMutationOptions, | 28 | gitCreateWorktreeMutationOptions, |
| 28 | } from "~/lib/gitReactQuery"; | 29 | } from "~/lib/gitReactQuery"; |
| | 30 | import { serverConfigQueryOptions } from "~/lib/serverReactQuery"; |
| 29 | | 31 | |
| 30 | import { isElectron } from "../env"; | 32 | import { isElectron } from "../env"; |
| 31 | import { buildBootstrapInput } from "../historyBootstrap"; | 33 | import { buildBootstrapInput } from "../historyBootstrap"; |
| ... | @@ -55,7 +57,6 @@ import { | ... | @@ -55,7 +57,6 @@ import { |
| 55 | isTerminalNewShortcut, | 57 | isTerminalNewShortcut, |
| 56 | isTerminalSplitShortcut, | 58 | isTerminalSplitShortcut, |
| 57 | isTerminalToggleShortcut, | 59 | isTerminalToggleShortcut, |
| 58 | type ResolvedTerminalKeybindings, | | |
| 59 | shortcutLabelForCommand, | 60 | shortcutLabelForCommand, |
| 60 | } from "../keybindings"; | 61 | } from "../keybindings"; |
| 61 | import ChatMarkdown from "./ChatMarkdown"; | 62 | import ChatMarkdown from "./ChatMarkdown"; |
| ... | @@ -90,6 +91,7 @@ const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; | ... | @@ -90,6 +91,7 @@ const MAX_VISIBLE_WORK_LOG_ENTRIES = 6; |
| 90 | const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`; | 91 | const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`; |
| 91 | const IMAGE_ONLY_BOOTSTRAP_PROMPT = | 92 | const IMAGE_ONLY_BOOTSTRAP_PROMPT = |
| 92 | "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]"; | 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 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { | 96 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| 95 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; | 97 | if (tone === "error") return "text-rose-300/50 dark:text-rose-300/50"; |
| ... | @@ -151,7 +153,6 @@ export default function ChatView() { | ... | @@ -151,7 +153,6 @@ export default function ChatView() { |
| 151 | const [expandedWorkGroups, setExpandedWorkGroups] = useState<Record<string, boolean>>({}); | 153 | const [expandedWorkGroups, setExpandedWorkGroups] = useState<Record<string, boolean>>({}); |
| 152 | const [nowTick, setNowTick] = useState(() => Date.now()); | 154 | const [nowTick, setNowTick] = useState(() => Date.now()); |
| 153 | const [terminalFocusRequestId, setTerminalFocusRequestId] = useState(0); | 155 | const [terminalFocusRequestId, setTerminalFocusRequestId] = useState(0); |
| 154 | const [terminalKeybindings, setTerminalKeybindings] = useState<ResolvedTerminalKeybindings>([]); | | |
| 155 | const messagesScrollRef = useRef<HTMLDivElement>(null); | 156 | const messagesScrollRef = useRef<HTMLDivElement>(null); |
| 156 | const messagesEndRef = useRef<HTMLDivElement>(null); | 157 | const messagesEndRef = useRef<HTMLDivElement>(null); |
| 157 | const shouldAutoScrollRef = useRef(true); | 158 | const shouldAutoScrollRef = useRef(true); |
| ... | @@ -262,6 +263,11 @@ export default function ChatView() { | ... | @@ -262,6 +263,11 @@ export default function ChatView() { |
| 262 | } as const); | 263 | } as const); |
| 263 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; | 264 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; |
| 264 | const branchesQuery = useQuery(gitBranchesQueryOptions(api, gitCwd)); | 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 | // Default true while loading to avoid toolbar flicker. | 271 | // Default true while loading to avoid toolbar flicker. |
| 266 | const isGitRepo = branchesQuery.data?.isRepo ?? true; | 272 | const isGitRepo = branchesQuery.data?.isRepo ?? true; |
| 267 | const splitTerminalShortcutLabel = useMemo( | 273 | const splitTerminalShortcutLabel = useMemo( |
| ... | @@ -279,30 +285,6 @@ export default function ChatView() { | ... | @@ -279,30 +285,6 @@ export default function ChatView() { |
| 279 | (activeThread.session !== null && activeThread.session.status !== "closed")), | 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 | const revokePreviewUrls = useCallback((images: Array<{ previewUrl?: string }>) => { | 288 | const revokePreviewUrls = useCallback((images: Array<{ previewUrl?: string }>) => { |
| 307 | for (const image of images) { | 289 | for (const image of images) { |
| 308 | if (!image.previewUrl) continue; | 290 | if (!image.previewUrl) continue; |