import { useCallback, useEffect, useRef, useState } from "react"; import useSWR from "swr"; import type { AgentApiClient } from "../api/client"; export function useAgentMessageInbox(client: AgentApiClient, sessionId: string | null) { const inbox = useSWR(sessionId ? ["agent-message-inbox", sessionId] : null, ([, id]) => client.listAgentMessages(id), { revalidateOnMount: false, revalidateOnFocus: true, shouldRetryOnError: false }); const [loadingMore, setLoadingMore] = useState(false); const mutate = inbox.mutate; const refresh = useCallback(() => { void mutate().catch(() => undefined); }, [mutate]); const refreshRef = useRef(refresh); useEffect(() => { refreshRef.current = refresh; }, [refresh]); useEffect(() => { if (!sessionId) return; const controller = new AbortController(); let timer: ReturnType | undefined; let retryDelay = 1500; let initialized = false; // Prefer the initial stream notification, but do not wait indefinitely for its first frame. const initialLoadTimer = setTimeout(() => { initialized = true; refreshRef.current(); }, 1500); function subscribe() { void client.streamAgentMessages(sessionId!, { signal: controller.signal, onMessage: () => { initialized = true; clearTimeout(initialLoadTimer); retryDelay = 1500; refreshRef.current(); } }) .catch(() => { // A failed subscription must not prevent the initial list from loading. if (!initialized && !controller.signal.aborted) { initialized = true; clearTimeout(initialLoadTimer); refreshRef.current(); } retryDelay = Math.min(retryDelay * 2, 30_000); }) .finally(() => { if (!controller.signal.aborted) timer = setTimeout(subscribe, retryDelay); }); } subscribe(); return () => { controller.abort(); clearTimeout(timer); clearTimeout(initialLoadTimer); }; }, [client, sessionId]); return { messages: inbox.data?.items ?? [], hasMore: Boolean(inbox.data?.next_cursor), loading: Boolean(sessionId) && (inbox.isLoading || (!inbox.data && !inbox.error)), error: inbox.error ? "任务消息暂时无法加载" : null, refresh, loadingMore, loadMore: async () => { if (!sessionId || !inbox.data?.next_cursor || loadingMore) return; setLoadingMore(true); try { const page = await client.listAgentMessages(sessionId, { cursor: inbox.data.next_cursor }); await inbox.mutate((current) => current ? { items: [...current.items, ...page.items.filter((item) => !current.items.some((existing) => existing.message_id === item.message_id))], next_cursor: page.next_cursor } : page, { revalidate: false }); } finally { setLoadingMore(false); } } }; }