refactor(app): drive agent panel focus from panes
This commit is contained in:
@@ -72,6 +72,7 @@ interface SplitContainerProps {
|
||||
newTabAgentOptionId?: "__new_tab_agent__";
|
||||
buildPaneContentModel: (input: {
|
||||
paneId: string;
|
||||
isPaneFocused: boolean;
|
||||
tab: WorkspaceTabDescriptor;
|
||||
}) => WorkspacePaneContentModel;
|
||||
onFocusPane: (paneId: string) => void;
|
||||
@@ -751,6 +752,7 @@ function SplitPaneView({
|
||||
activeTabDescriptor
|
||||
? buildPaneContentModel({
|
||||
paneId: pane.id,
|
||||
isPaneFocused: isFocused,
|
||||
tab: activeTabDescriptor,
|
||||
})
|
||||
: null,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useFocusEffect, useIsFocused } from "@react-navigation/native";
|
||||
import { ActivityIndicator, Platform, Text, View } from "react-native";
|
||||
import ReanimatedAnimated from "react-native-reanimated";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
@@ -102,12 +101,13 @@ function useAgentPanelDescriptor(
|
||||
}
|
||||
|
||||
function AgentPanel() {
|
||||
const { serverId, target, openFileInWorkspace } = usePaneContext();
|
||||
const { serverId, target, isPaneFocused, openFileInWorkspace } = usePaneContext();
|
||||
invariant(target.kind === "agent", "AgentPanel requires agent target");
|
||||
return (
|
||||
<AgentPanelContent
|
||||
serverId={serverId}
|
||||
agentId={target.agentId}
|
||||
isPaneFocused={isPaneFocused}
|
||||
onOpenWorkspaceFile={({ filePath }) => {
|
||||
openFileInWorkspace(filePath);
|
||||
}}
|
||||
@@ -142,10 +142,12 @@ function isNotFoundErrorMessage(message: string): boolean {
|
||||
function AgentPanelContent({
|
||||
serverId,
|
||||
agentId,
|
||||
isPaneFocused,
|
||||
onOpenWorkspaceFile,
|
||||
}: {
|
||||
serverId: string;
|
||||
agentId: string;
|
||||
isPaneFocused: boolean;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
}) {
|
||||
const resolvedAgentId = agentId.trim() || undefined;
|
||||
@@ -185,6 +187,7 @@ function AgentPanelContent({
|
||||
<AgentPanelBody
|
||||
serverId={resolvedServerId}
|
||||
agentId={resolvedAgentId}
|
||||
isPaneFocused={isPaneFocused}
|
||||
client={runtimeClient}
|
||||
isConnected={runtimeIsConnected}
|
||||
connectionStatus={connectionStatus}
|
||||
@@ -196,6 +199,7 @@ function AgentPanelContent({
|
||||
function AgentPanelBody({
|
||||
serverId,
|
||||
agentId,
|
||||
isPaneFocused,
|
||||
client,
|
||||
isConnected,
|
||||
connectionStatus,
|
||||
@@ -203,6 +207,7 @@ function AgentPanelBody({
|
||||
}: {
|
||||
serverId: string;
|
||||
agentId?: string;
|
||||
isPaneFocused: boolean;
|
||||
client: NonNullable<ReturnType<typeof useHostRuntimeSession>["client"]>;
|
||||
isConnected: boolean;
|
||||
connectionStatus: HostRuntimeConnectionStatus;
|
||||
@@ -213,7 +218,8 @@ function AgentPanelBody({
|
||||
const { isArchivingAgent } = useArchiveAgent();
|
||||
const streamViewRef = useRef<AgentStreamViewHandle>(null);
|
||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||
const isScreenFocused = useIsFocused();
|
||||
const clearOnAgentBlurRef = useRef<() => void>(() => {});
|
||||
const wasPaneFocusedRef = useRef(isPaneFocused);
|
||||
const reconnectToastArmedRef = useRef(false);
|
||||
const initAttemptTokenRef = useRef(0);
|
||||
const routeBottomAnchorRequestRef = useRef<{
|
||||
@@ -314,21 +320,17 @@ function AgentPanelBody({
|
||||
return filtered;
|
||||
}, [agentId, allPendingPermissions]);
|
||||
|
||||
const setFocusedAgentId = useCallback(
|
||||
(focusedAgentId: string | null) => {
|
||||
useSessionStore.getState().setFocusedAgentId(serverId, focusedAgentId);
|
||||
},
|
||||
[serverId]
|
||||
);
|
||||
|
||||
const attentionController = useAgentAttentionClear({
|
||||
agentId,
|
||||
client,
|
||||
isConnected,
|
||||
requiresAttention: agent?.requiresAttention,
|
||||
attentionReason: agent?.attentionReason,
|
||||
isScreenFocused,
|
||||
isScreenFocused: isPaneFocused,
|
||||
});
|
||||
useEffect(() => {
|
||||
clearOnAgentBlurRef.current = attentionController.clearOnAgentBlur;
|
||||
}, [attentionController.clearOnAgentBlur]);
|
||||
|
||||
const { style: animatedKeyboardStyle } = useKeyboardShiftStyle({
|
||||
mode: "translate",
|
||||
@@ -383,34 +385,38 @@ function AgentPanelBody({
|
||||
}
|
||||
}, [connectionStatus, panelToast.api]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (!agentId || !isConnected || !hasSession) {
|
||||
return;
|
||||
}
|
||||
ensureInitializedWithSyncErrorHandling("focus");
|
||||
}, [agentId, ensureInitializedWithSyncErrorHandling, hasSession, isConnected])
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!isPaneFocused || !agentId || !isConnected || !hasSession) {
|
||||
return;
|
||||
}
|
||||
ensureInitializedWithSyncErrorHandling("focus");
|
||||
}, [
|
||||
agentId,
|
||||
ensureInitializedWithSyncErrorHandling,
|
||||
hasSession,
|
||||
isConnected,
|
||||
isPaneFocused,
|
||||
]);
|
||||
|
||||
const isArchivingCurrentAgent = Boolean(
|
||||
agentId &&
|
||||
isArchivingAgent({ serverId, agentId })
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (!agentId) {
|
||||
setFocusedAgentId(null);
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (wasPaneFocusedRef.current && !isPaneFocused) {
|
||||
clearOnAgentBlurRef.current();
|
||||
}
|
||||
wasPaneFocusedRef.current = isPaneFocused;
|
||||
}, [isPaneFocused]);
|
||||
|
||||
setFocusedAgentId(agentId);
|
||||
return () => {
|
||||
attentionController.clearOnAgentBlur();
|
||||
setFocusedAgentId(null);
|
||||
};
|
||||
}, [agentId, attentionController, setFocusedAgentId])
|
||||
);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (wasPaneFocusedRef.current) {
|
||||
clearOnAgentBlurRef.current();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isInitializing = agentId ? isInitializingFromMap !== false : false;
|
||||
const isHistorySyncing = useMemo(() => {
|
||||
@@ -797,7 +803,7 @@ function AgentPanelBody({
|
||||
<AgentInputArea
|
||||
agentId={agentId}
|
||||
serverId={serverId}
|
||||
autoFocus
|
||||
autoFocus={isPaneFocused}
|
||||
isSubmitLoading={showPendingCreateSubmitLoading}
|
||||
onAttentionInputFocus={attentionController.clearOnInputFocus}
|
||||
onAttentionPromptSend={attentionController.clearOnPromptSend}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface PaneContextValue {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
tabId: string;
|
||||
isPaneFocused: boolean;
|
||||
target: WorkspaceTabTarget;
|
||||
openTab(target: WorkspaceTabTarget): void;
|
||||
closeCurrentTab(): void;
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface BuildWorkspacePaneContentModelInput {
|
||||
tab: WorkspaceTabDescriptor;
|
||||
normalizedServerId: string;
|
||||
normalizedWorkspaceId: string;
|
||||
isPaneFocused: boolean;
|
||||
onOpenTab: (target: WorkspaceTabDescriptor["target"]) => void;
|
||||
onCloseCurrentTab: () => void;
|
||||
onRetargetCurrentTab: (target: WorkspaceTabDescriptor["target"]) => void;
|
||||
@@ -25,6 +26,7 @@ export function buildWorkspacePaneContentModel({
|
||||
tab,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
isPaneFocused,
|
||||
onOpenTab,
|
||||
onCloseCurrentTab,
|
||||
onRetargetCurrentTab,
|
||||
@@ -40,6 +42,7 @@ export function buildWorkspacePaneContentModel({
|
||||
serverId: normalizedServerId,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
tabId: tab.tabId,
|
||||
isPaneFocused,
|
||||
target: tab.target,
|
||||
openTab: onOpenTab,
|
||||
closeCurrentTab: onCloseCurrentTab,
|
||||
|
||||
@@ -873,6 +873,24 @@ function WorkspaceScreenContent({
|
||||
}),
|
||||
[uiTabs, workspaceLayout]
|
||||
);
|
||||
const setFocusedAgentId = useSessionStore((state) => state.setFocusedAgentId);
|
||||
const focusedPaneAgentId = useMemo(() => {
|
||||
const target = focusedPaneTabState.activeTab?.descriptor.target;
|
||||
if (target?.kind !== "agent") {
|
||||
return null;
|
||||
}
|
||||
return target.agentId;
|
||||
}, [focusedPaneTabState.activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
setFocusedAgentId(normalizedServerId, focusedPaneAgentId);
|
||||
}, [focusedPaneAgentId, normalizedServerId, setFocusedAgentId]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setFocusedAgentId(normalizedServerId, null);
|
||||
};
|
||||
}, [normalizedServerId, setFocusedAgentId]);
|
||||
|
||||
const ensureWorkspaceTab = useCallback(
|
||||
function ensureWorkspaceTab(target: WorkspaceTabTarget): string | null {
|
||||
@@ -1833,12 +1851,14 @@ function WorkspaceScreenContent({
|
||||
(input: {
|
||||
tab: WorkspaceTabDescriptor;
|
||||
paneId?: string | null;
|
||||
isPaneFocused: boolean;
|
||||
focusPaneBeforeOpen?: boolean;
|
||||
}) =>
|
||||
buildWorkspacePaneContentModel({
|
||||
tab: input.tab,
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
isPaneFocused: input.isPaneFocused,
|
||||
onOpenTab: (target) => {
|
||||
if (!persistenceKey) {
|
||||
return;
|
||||
@@ -1885,6 +1905,7 @@ function WorkspaceScreenContent({
|
||||
? buildPaneContentModel({
|
||||
tab: activeTabDescriptor,
|
||||
paneId: focusedPaneTabState.pane?.id ?? null,
|
||||
isPaneFocused: true,
|
||||
focusPaneBeforeOpen: false,
|
||||
})
|
||||
: null,
|
||||
@@ -2159,13 +2180,14 @@ function WorkspaceScreenContent({
|
||||
onCloseOtherTabs={handleCloseOtherTabsInPane}
|
||||
onSelectNewTabOption={handleSelectNewTabOption}
|
||||
newTabAgentOptionId={NEW_TAB_AGENT_OPTION_ID}
|
||||
buildPaneContentModel={({ paneId, tab }) =>
|
||||
buildPaneContentModel({
|
||||
tab,
|
||||
paneId,
|
||||
focusPaneBeforeOpen: true,
|
||||
})
|
||||
}
|
||||
buildPaneContentModel={({ paneId, tab, isPaneFocused }) =>
|
||||
buildPaneContentModel({
|
||||
tab,
|
||||
paneId,
|
||||
isPaneFocused,
|
||||
focusPaneBeforeOpen: true,
|
||||
})
|
||||
}
|
||||
onFocusPane={(paneId) => {
|
||||
focusWorkspacePane(persistenceKey, paneId);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user