153 lines
4.6 KiB
TypeScript
153 lines
4.6 KiB
TypeScript
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]),
|
|
};
|
|
}
|