fix: Threads pagination (#642)

* fix: Threads pagination

* cr
This commit is contained in:
Brace Sproul 2025-08-01 13:04:04 -07:00 • committed by GitHub
parent 46b435a543
commit c70da1b1c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 106 additions and 20 deletions

View file

@ -31,10 +31,22 @@ type FilterStatus =
function AllThreadsPageContent() {
const router = useRouter();
const limit = 25;
const [offset, setOffset] = useState(0);
const { currentInstallation, installationsLoading } = useGitHubAppProvider();
const { threads, isLoading: threadsLoading } = useThreadsSWR({
const {
threads,
isLoading: threadsLoading,
hasMore,
} = useThreadsSWR({
assistantId: MANAGER_GRAPH_ID,
currentInstallation,
pagination: {
limit,
offset,
sortBy: "updated_at",
sortOrder: "desc",
},
});
const [searchQuery, setSearchQuery] = useState("");
const [statusFilter, setStatusFilter] = useState<FilterStatus>("all");
@ -96,6 +108,11 @@ function AllThreadsPageContent() {
(statusFilter === "all"
? Object.values(groupedThreads).flat().length === 0
: filteredThreads.length === 0);
const showNoThreads =
filteredThreads.length === 0 &&
!threadsLoading &&
!statusLoading &&
!showThreadsLoading;
return (
<div className="bg-background flex h-screen flex-col">
@ -233,22 +250,18 @@ function AllThreadsPageContent() {
</div>
)}
{filteredThreads.length === 0 &&
!threadsLoading &&
!statusLoading && (
<div className="py-12 text-center">
<div className="text-muted-foreground mb-2">
No threads found
</div>
<div className="text-muted-foreground/70 text-xs">
{!threads || threads.length === 0
? "No threads have been created yet"
: searchQuery
? "Try adjusting your search query"
: "No threads match the selected filter"}
</div>
{showNoThreads && (
<div className="py-12 text-center">
<div className="text-muted-foreground mb-2">No threads found</div>
<div className="text-muted-foreground/70 text-xs">
{!threads || threads.length === 0
? "No threads have been created yet"
: searchQuery
? "Try adjusting your search query"
: "No threads match the selected filter"}
</div>
)}
</div>
)}
{showThreadsLoading && (
<div>
@ -264,6 +277,16 @@ function AllThreadsPageContent() {
</div>
</div>
)}
{!showNoThreads && hasMore && (
<div className="flex items-center justify-center">
<Button
variant="outline"
onClick={() => setOffset((prev) => prev + limit)}
>
Load more
</Button>
</div>
)}
</div>
</div>
</div>

View file

@ -6,9 +6,11 @@ import { ManagerGraphState } from "@open-swe/shared/open-swe/manager/types";
import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types";
import { ReviewerGraphState } from "@open-swe/shared/open-swe/reviewer/types";
import { GraphState } from "@open-swe/shared/open-swe/types";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import type { Installation } from "./useGitHubInstallations";
type ThreadSortBy = "thread_id" | "status" | "created_at" | "updated_at";
type SortOrder = "asc" | "desc";
/**
* Union type representing all possible graph states in the Open SWE system
*/
@ -25,6 +27,32 @@ interface UseThreadsSWROptions {
revalidateOnReconnect?: boolean;
currentInstallation?: Installation | null;
disableOrgFiltering?: boolean;
/**
* Pagination options
*/
pagination?: {
/**
* Maximum number of threads to return.
* @default 25
*/
limit?: number;
/**
* Offset to start from.
* @default 0
*/
offset?: number;
/**
* Sort by.
* @default "updated_at"
*/
sortBy?: ThreadSortBy;
/**
* Sort order.
* Must be one of 'asc' or 'desc'.
* @default "desc"
*/
sortOrder?: SortOrder;
};
}
/**
@ -45,12 +73,34 @@ export function useThreadsSWR<
revalidateOnReconnect = THREAD_SWR_CONFIG.revalidateOnReconnect,
currentInstallation,
disableOrgFiltering,
pagination,
} = options;
const [hasMoreState, setHasMoreState] = useState(true);
const paginationWithDefaults = {
limit: 25,
offset: 0,
sortBy: "updated_at" as ThreadSortBy,
sortOrder: "desc" as SortOrder,
...pagination,
};
const apiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL ?? "";
// Create a unique key for SWR caching based on assistantId
const swrKey = assistantId ? ["threads", assistantId] : ["threads", "all"];
// Create a unique key for SWR caching based on assistantId and pagination parameters
const swrKey = useMemo(() => {
const baseKey = assistantId ? ["threads", assistantId] : ["threads", "all"];
if (pagination) {
return [
...baseKey,
paginationWithDefaults.limit,
paginationWithDefaults.offset,
paginationWithDefaults.sortBy,
paginationWithDefaults.sortOrder,
];
}
return baseKey;
}, [assistantId, paginationWithDefaults]);
const fetcher = async (): Promise<Thread<TGraphState>[]> => {
if (!apiUrl) {
@ -63,8 +113,11 @@ export function useThreadsSWR<
metadata: {
graph_id: assistantId,
},
...(paginationWithDefaults ? paginationWithDefaults : {}),
}
: undefined;
: paginationWithDefaults
? paginationWithDefaults
: undefined;
return await client.threads.search<TGraphState>(searchArgs);
};
@ -89,7 +142,12 @@ export function useThreadsSWR<
return allThreads;
}
if (!allThreads.length) {
setHasMoreState(false);
}
if (!currentInstallation) {
setHasMoreState(false);
return [];
}
@ -102,11 +160,16 @@ export function useThreadsSWR<
});
}, [data, currentInstallation, disableOrgFiltering]);
const hasMore = useMemo(() => {
return hasMoreState && !!threads.length;
}, [threads, paginationWithDefaults]);
return {
threads,
error,
isLoading,
isValidating,
mutate,
hasMore,
};
}