Refine agent list attention handling
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { AppState, Platform } from "react-native";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import {
|
||||
shouldClearAgentAttention,
|
||||
type AgentAttentionClearTrigger,
|
||||
} from "@/utils/agent-attention";
|
||||
|
||||
type AttentionReason = "finished" | "error" | "permission" | null | undefined;
|
||||
|
||||
interface UseAgentAttentionClearParams {
|
||||
agentId: string | null | undefined;
|
||||
client: DaemonClient | null;
|
||||
isConnected: boolean;
|
||||
requiresAttention: boolean | null | undefined;
|
||||
attentionReason: AttentionReason;
|
||||
isScreenFocused: boolean;
|
||||
}
|
||||
|
||||
interface AgentAttentionClearController {
|
||||
clearOnInputFocus: () => void;
|
||||
clearOnPromptSend: () => void;
|
||||
clearOnAgentBlur: () => void;
|
||||
}
|
||||
|
||||
function getIsAppVisible(): boolean {
|
||||
const isAppStateActive = AppState.currentState === "active";
|
||||
if (Platform.OS !== "web") {
|
||||
return isAppStateActive;
|
||||
}
|
||||
const documentVisible =
|
||||
typeof document === "undefined" || document.visibilityState === "visible";
|
||||
const windowFocused =
|
||||
typeof document === "undefined" ||
|
||||
typeof document.hasFocus !== "function" ||
|
||||
document.hasFocus();
|
||||
return isAppStateActive && documentVisible && windowFocused;
|
||||
}
|
||||
|
||||
export function useAgentAttentionClear({
|
||||
agentId,
|
||||
client,
|
||||
isConnected,
|
||||
requiresAttention,
|
||||
attentionReason,
|
||||
isScreenFocused,
|
||||
}: UseAgentAttentionClearParams): AgentAttentionClearController {
|
||||
const [isAppVisible, setIsAppVisible] = useState<boolean>(() => getIsAppVisible());
|
||||
const deferredFocusEntryClearRef = useRef(false);
|
||||
const prevRequiresAttentionRef = useRef(Boolean(requiresAttention));
|
||||
const prevActivelyViewedRef = useRef(isScreenFocused && getIsAppVisible());
|
||||
const prevScreenFocusedRef = useRef(false);
|
||||
const prevAppVisibleRef = useRef(getIsAppVisible());
|
||||
|
||||
const clearAttention = useCallback(
|
||||
(trigger: AgentAttentionClearTrigger) => {
|
||||
const resolvedAgentId = agentId?.trim();
|
||||
if (!client || !resolvedAgentId) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!shouldClearAgentAttention({
|
||||
agentId: resolvedAgentId,
|
||||
isConnected,
|
||||
requiresAttention,
|
||||
attentionReason,
|
||||
trigger,
|
||||
hasDeferredFocusEntryClear: deferredFocusEntryClearRef.current,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
deferredFocusEntryClearRef.current = false;
|
||||
client.clearAgentAttention(resolvedAgentId);
|
||||
},
|
||||
[agentId, attentionReason, client, isConnected, requiresAttention]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const updateVisibility = () => {
|
||||
setIsAppVisible(getIsAppVisible());
|
||||
};
|
||||
|
||||
const appStateSubscription = AppState.addEventListener(
|
||||
"change",
|
||||
updateVisibility
|
||||
);
|
||||
|
||||
if (Platform.OS === "web" && typeof document !== "undefined") {
|
||||
document.addEventListener("visibilitychange", updateVisibility);
|
||||
window.addEventListener("focus", updateVisibility);
|
||||
window.addEventListener("blur", updateVisibility);
|
||||
|
||||
return () => {
|
||||
appStateSubscription.remove();
|
||||
document.removeEventListener("visibilitychange", updateVisibility);
|
||||
window.removeEventListener("focus", updateVisibility);
|
||||
window.removeEventListener("blur", updateVisibility);
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
appStateSubscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!requiresAttention) {
|
||||
deferredFocusEntryClearRef.current = false;
|
||||
}
|
||||
}, [requiresAttention]);
|
||||
|
||||
useEffect(() => {
|
||||
const isActivelyViewed = isScreenFocused && isAppVisible;
|
||||
if (
|
||||
!prevRequiresAttentionRef.current &&
|
||||
Boolean(requiresAttention) &&
|
||||
prevActivelyViewedRef.current &&
|
||||
isActivelyViewed
|
||||
) {
|
||||
deferredFocusEntryClearRef.current = true;
|
||||
}
|
||||
prevRequiresAttentionRef.current = Boolean(requiresAttention);
|
||||
prevActivelyViewedRef.current = isActivelyViewed;
|
||||
}, [isAppVisible, isScreenFocused, requiresAttention]);
|
||||
|
||||
useEffect(() => {
|
||||
const enteredScreenFocus =
|
||||
!prevScreenFocusedRef.current && isScreenFocused && isAppVisible;
|
||||
const resumedIntoFocusedAgent =
|
||||
!prevAppVisibleRef.current && isAppVisible && isScreenFocused;
|
||||
|
||||
if (enteredScreenFocus || resumedIntoFocusedAgent) {
|
||||
clearAttention("focus-entry");
|
||||
}
|
||||
|
||||
prevScreenFocusedRef.current = isScreenFocused;
|
||||
prevAppVisibleRef.current = isAppVisible;
|
||||
}, [clearAttention, isAppVisible, isScreenFocused]);
|
||||
|
||||
return {
|
||||
clearOnInputFocus: useCallback(() => {
|
||||
clearAttention("input-focus");
|
||||
}, [clearAttention]),
|
||||
clearOnPromptSend: useCallback(() => {
|
||||
clearAttention("prompt-send");
|
||||
}, [clearAttention]),
|
||||
clearOnAgentBlur: useCallback(() => {
|
||||
clearAttention("agent-blur");
|
||||
}, [clearAttention]),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user