feat: implement infinite scrolling for native sessions and add reset functionality to queue scheduler

This commit is contained in:
catlog22
2026-02-27 21:24:44 +08:00
parent a581a2e62b
commit 9be35ed5fb
12 changed files with 263 additions and 131 deletions

View File

@@ -4,7 +4,7 @@
// TanStack Query hook for native CLI sessions list
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
import {
fetchNativeSessions,
type NativeSessionListItem,
@@ -17,6 +17,7 @@ import { workspaceQueryKeys } from '@/lib/queryKeys';
const STALE_TIME = 2 * 60 * 1000; // 2 minutes (increased from 30s)
const GC_TIME = 10 * 60 * 1000; // 10 minutes (increased from 5min)
const PAGE_SIZE = 50; // Default page size for pagination
// ========== Types ==========
@@ -54,6 +55,29 @@ export interface UseNativeSessionsReturn {
refetch: () => Promise<void>;
}
export interface UseNativeSessionsInfiniteReturn {
/** All sessions data (flattened from all pages) */
sessions: NativeSessionListItem[];
/** Sessions grouped by tool */
byTool: ByToolRecord;
/** Total count from current pages */
count: number;
/** Loading state for initial fetch */
isLoading: boolean;
/** Fetching state (initial or refetch) */
isFetching: boolean;
/** Fetching next page */
isFetchingNextPage: boolean;
/** Whether there are more pages */
hasNextPage: boolean;
/** Error object if query failed */
error: Error | null;
/** Fetch next page */
fetchNextPage: () => Promise<void>;
/** Manually refetch data */
refetch: () => Promise<void>;
}
// ========== Helper Functions ==========
/**
@@ -95,7 +119,7 @@ export function useNativeSessions(
const query = useQuery<NativeSessionsListResponse>({
queryKey: workspaceQueryKeys.nativeSessionsList(projectPath, tool),
queryFn: () => fetchNativeSessions(tool, projectPath),
queryFn: () => fetchNativeSessions({ tool, project: projectPath, limit: 200 }),
staleTime,
gcTime,
enabled,
@@ -107,7 +131,7 @@ export function useNativeSessions(
// Memoize sessions and byTool calculations
const { sessions, byTool } = React.useMemo(() => {
const sessions = query.data?.sessions ?? [];
const byTool = groupByTool(sessions);
const byTool = query.data?.byTool ?? groupByTool(sessions);
return { sessions, byTool };
}, [query.data]);
@@ -125,3 +149,81 @@ export function useNativeSessions(
refetch,
};
}
/**
* Hook for fetching native CLI sessions with infinite scroll/pagination
*
* @example
* ```tsx
* const { sessions, fetchNextPage, hasNextPage, isFetchingNextPage } = useNativeSessionsInfinite();
*
* // Load more button
* <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
* {isFetchingNextPage ? 'Loading...' : 'Load More'}
* </button>
* ```
*/
export function useNativeSessionsInfinite(
options: UseNativeSessionsOptions = {}
): UseNativeSessionsInfiniteReturn {
const { tool, staleTime = STALE_TIME, gcTime = GC_TIME, enabled = true } = options;
const projectPath = useWorkflowStore(selectProjectPath);
const query = useInfiniteQuery<NativeSessionsListResponse>({
queryKey: [...workspaceQueryKeys.nativeSessionsList(projectPath, tool), 'infinite'],
queryFn: ({ pageParam }) => fetchNativeSessions({
tool,
project: projectPath,
limit: PAGE_SIZE,
cursor: pageParam as string | null,
}),
initialPageParam: null as string | null,
getNextPageParam: (lastPage) => {
if (!lastPage.hasMore || !lastPage.nextCursor) return undefined;
return lastPage.nextCursor;
},
staleTime,
gcTime,
enabled,
refetchOnWindowFocus: false,
retry: 2,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
});
// Flatten all pages into a single array and group by tool
const { sessions, byTool, count } = React.useMemo(() => {
const allSessions = query.data?.pages.flatMap(page => page.sessions) ?? [];
// Merge byTool from all pages
const mergedByTool: ByToolRecord = {};
for (const page of query.data?.pages ?? []) {
for (const [toolKey, toolSessions] of Object.entries(page.byTool ?? {})) {
if (!mergedByTool[toolKey]) {
mergedByTool[toolKey] = [];
}
mergedByTool[toolKey].push(...toolSessions);
}
}
return { sessions: allSessions, byTool: mergedByTool, count: allSessions.length };
}, [query.data]);
const fetchNextPage = async () => {
await query.fetchNextPage();
};
const refetch = async () => {
await query.refetch();
};
return {
sessions,
byTool,
count,
isLoading: query.isLoading,
isFetching: query.isFetching,
isFetchingNextPage: query.isFetchingNextPage,
hasNextPage: query.hasNextPage,
error: query.error,
fetchNextPage,
refetch,
};
}