From 6ae7f0c0eeeb0a694c6fc3e728c562975be57f16 Mon Sep 17 00:00:00 2001 From: aworki <1224518406@qq.com> Date: Tue, 10 Mar 2026 23:11:08 +0800 Subject: [PATCH] fix: load all thread pages in thread lists (#1044) * fix(frontend): load all thread pages in thread lists * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Willem Jiang Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/app/mock/api/threads/search/route.ts | 77 ++++++++++++++++--- frontend/src/core/threads/hooks.ts | 51 +++++++++++- 2 files changed, 116 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/mock/api/threads/search/route.ts b/frontend/src/app/mock/api/threads/search/route.ts index 93c4834..74ba79a 100644 --- a/frontend/src/app/mock/api/threads/search/route.ts +++ b/frontend/src/app/mock/api/threads/search/route.ts @@ -1,27 +1,84 @@ import fs from "fs"; import path from "path"; -export function POST() { +type ThreadSearchRequest = { + limit?: number; + offset?: number; + sortBy?: "updated_at" | "created_at"; + sortOrder?: "asc" | "desc"; +}; + +type MockThreadSearchResult = Record & { + thread_id: string; + updated_at: string | undefined; +}; + +export async function POST(request: Request) { + const body = ((await request.json().catch(() => ({}))) ?? {}) as ThreadSearchRequest; + + const rawLimit = body.limit; + let limit = 50; + if (typeof rawLimit === "number") { + const normalizedLimit = Math.max(0, Math.floor(rawLimit)); + if (!Number.isNaN(normalizedLimit)) { + limit = normalizedLimit; + } + } + + const rawOffset = body.offset; + let offset = 0; + if (typeof rawOffset === "number") { + const normalizedOffset = Math.max(0, Math.floor(rawOffset)); + if (!Number.isNaN(normalizedOffset)) { + offset = normalizedOffset; + } + } + const sortBy = body.sortBy ?? "updated_at"; + const sortOrder = body.sortOrder ?? "desc"; + const threadsDir = fs.readdirSync( path.resolve(process.cwd(), "public/demo/threads"), { withFileTypes: true, }, ); + const threadData = threadsDir - .map((threadId) => { + .map((threadId) => { if (threadId.isDirectory() && !threadId.name.startsWith(".")) { - const threadData = fs.readFileSync( - path.resolve(`public/demo/threads/${threadId.name}/thread.json`), - "utf8", - ); + const threadData = JSON.parse( + fs.readFileSync( + path.resolve(`public/demo/threads/${threadId.name}/thread.json`), + "utf8", + ), + ) as Record; + return { + ...threadData, thread_id: threadId.name, - values: JSON.parse(threadData).values, + updated_at: + typeof threadData.updated_at === "string" + ? threadData.updated_at + : typeof threadData.created_at === "string" + ? threadData.created_at + : undefined, }; } - return false; + return null; }) - .filter(Boolean); - return Response.json(threadData); + .filter((thread): thread is MockThreadSearchResult => thread !== null) + .sort((a, b) => { + const aTimestamp = a[sortBy]; + const bTimestamp = b[sortBy]; + const aParsed = + typeof aTimestamp === "string" ? Date.parse(aTimestamp) : 0; + const bParsed = + typeof bTimestamp === "string" ? Date.parse(bTimestamp) : 0; + const aValue = Number.isNaN(aParsed) ? 0 : aParsed; + const bValue = Number.isNaN(bParsed) ? 0 : bParsed; + return sortOrder === "asc" ? aValue - bValue : bValue - aValue; + }); + + const pagedThreads = threadData.slice(offset, offset + limit); + return Response.json(pagedThreads); } diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index 9a8a11f..21ae54b 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -372,8 +372,55 @@ export function useThreads( return useQuery({ queryKey: ["threads", "search", params], queryFn: async () => { - const response = await apiClient.threads.search(params); - return response as AgentThread[]; + const maxResults = params.limit; + const initialOffset = params.offset ?? 0; + const DEFAULT_PAGE_SIZE = 50; + + // Preserve prior semantics: if a non-positive limit is explicitly provided, + // delegate to a single search call with the original parameters. + if (maxResults !== undefined && maxResults <= 0) { + const response = await apiClient.threads.search(params); + return response as AgentThread[]; + } + + const pageSize = + typeof maxResults === "number" && maxResults > 0 + ? Math.min(DEFAULT_PAGE_SIZE, maxResults) + : DEFAULT_PAGE_SIZE; + + const threads: AgentThread[] = []; + let offset = initialOffset; + + while (true) { + if (typeof maxResults === "number" && threads.length >= maxResults) { + break; + } + + const currentLimit = + typeof maxResults === "number" + ? Math.min(pageSize, maxResults - threads.length) + : pageSize; + + if (typeof maxResults === "number" && currentLimit <= 0) { + break; + } + + const response = (await apiClient.threads.search({ + ...params, + limit: currentLimit, + offset, + })) as AgentThread[]; + + threads.push(...response); + + if (response.length < currentLimit) { + break; + } + + offset += response.length; + } + + return threads; }, refetchOnWindowFocus: false, });