History

rm pagination

Rhys Sullivan committed 8 months ago

Commit 8b105e81 parent f0963694 files · +23 −155Browse files

Unified · Split

4 files
  1. executor/apps/web/src/hooks/use/workspace-tools.ts +19 −155
  2. executor/packages/database/convex/_generated/api.d.ts +1 −0
  3. executor/packages/database/convex/executorNode.ts +2 −0
  4. executor/packages/database/src/runtime/workspace_tools.ts +1 −0
executor/apps/web/src/hooks/use/workspace-tools.ts+19−155
@@ -1,7 +1,7 @@
1"use client";1"use client";
2 2
3import { useCallback, useEffect, useMemo, useRef, useState } from "react";3import { useCallback, useEffect, useRef } from "react";
4import { useInfiniteQuery, type InfiniteData } from "@tanstack/react-query";4import { useQuery } from "@tanstack/react-query";
5import { useAction, useQuery as useConvexQuery } from "convex/react";5import { useAction, useQuery as useConvexQuery } from "convex/react";
6import { convexApi } from "@/lib/convex-api";6import { convexApi } from "@/lib/convex-api";
7import type { OpenApiSourceQuality, SourceAuthProfile, ToolDescriptor } from "@/lib/types";7import type { OpenApiSourceQuality, SourceAuthProfile, ToolDescriptor } from "@/lib/types";
@@ -55,22 +55,9 @@ type ListToolsWithWarningsAction = (args: { · 47 unmodified lines
55 cursor?: string;55 cursor?: string;
56 limit?: number;56 limit?: number;
57 buildId?: string;57 buildId?: string;
58 fetchAll?: boolean;
58}) => Promise<WorkspaceToolsQueryResult>;59}) => Promise<WorkspaceToolsQueryResult>;
59 60
60interface ToolInventoryPageParam {
61 cursor?: string;
62 buildId?: string;
63}
64
65interface SourceToolPageState {
66 tools: ToolDescriptor[];
67 nextCursor?: string | null;
68 buildId?: string;
69 loading: boolean;
70}
71
72const DEFAULT_PAGE_SIZE = 250;
73
74/**61/**
75 * Fetches tool metadata from a Convex action, cached by TanStack Query.62 * Fetches tool metadata from a Convex action, cached by TanStack Query.
76 *63 *
@@ -85,7 +72,6 @@ export function useWorkspaceTools( · 8 unmodified lines
85 const listToolsWithWarningsRaw = useAction(convexApi.executorNode.listToolsWithWarnings);72 const listToolsWithWarningsRaw = useAction(convexApi.executorNode.listToolsWithWarnings);
86 const listToolsWithWarnings = listToolsWithWarningsRaw as ListToolsWithWarningsAction;73 const listToolsWithWarnings = listToolsWithWarningsRaw as ListToolsWithWarningsAction;
87 const detailsCacheRef = useRef<Map<string, ToolDescriptor>>(new Map());74 const detailsCacheRef = useRef<Map<string, ToolDescriptor>>(new Map());
88 const [sourcePages, setSourcePages] = useState<Record<string, SourceToolPageState>>({});
89 75
90 // Watch tool sources reactively so we invalidate when sources change76 // Watch tool sources reactively so we invalidate when sources change
91 const toolSources = useConvexQuery(77 const toolSources = useConvexQuery(
@@ -93,7 +79,7 @@ export function useWorkspaceTools( · 1 unmodified line
93 context ? { workspaceId: context.workspaceId, sessionId: context.sessionId } : "skip",79 context ? { workspaceId: context.workspaceId, sessionId: context.sessionId } : "skip",
94 );80 );
95 81
96 const inventoryQuery = useInfiniteQuery<WorkspaceToolsQueryResult, Error, InfiniteData<WorkspaceToolsQueryResult>, unknown[], ToolInventoryPageParam>({82 const inventoryQuery = useQuery<WorkspaceToolsQueryResult, Error>({
97 queryKey: [83 queryKey: [
98 "workspace-tools-inventory",84 "workspace-tools-inventory",
99 context?.workspaceId,85 context?.workspaceId,
@@ -102,7 +88,7 @@ export function useWorkspaceTools( · 2 unmodified lines
102 includeDetails,88 includeDetails,
103 toolSources,89 toolSources,
104 ],90 ],
105 queryFn: async ({ pageParam }): Promise<WorkspaceToolsQueryResult> => {91 queryFn: async (): Promise<WorkspaceToolsQueryResult> => {
106 if (!context) {92 if (!context) {
107 return {93 return {
108 tools: [],94 tools: [],
@@ -127,27 +113,12 @@ export function useWorkspaceTools( · 18 unmodified lines
127 ...(context.clientId && { clientId: context.clientId }),113 ...(context.clientId && { clientId: context.clientId }),
128 ...(context.sessionId && { sessionId: context.sessionId }),114 ...(context.sessionId && { sessionId: context.sessionId }),
129 includeDetails,115 includeDetails,
130 limit: DEFAULT_PAGE_SIZE,116 fetchAll: true,
131 ...(pageParam?.cursor ? { cursor: pageParam.cursor } : {}),
132 ...(pageParam?.buildId ? { buildId: pageParam.buildId } : {}),
133 });117 });
134 },118 },
135 initialPageParam: {},
136 getNextPageParam: (lastPage) => {
137 const nextCursor = lastPage.nextCursor;
138 if (!nextCursor) {
139 return undefined;
140 }
141 return {
142 cursor: nextCursor,
143 buildId: lastPage.inventoryStatus.readyBuildId,
144 };
145 },
146 enabled: !!context,119 enabled: !!context,
147 refetchInterval: (query) => {120 refetchInterval: (query) => {
148 const data = query.state.data as InfiniteData<WorkspaceToolsQueryResult> | undefined;121 const state = query.state.data?.inventoryStatus.state;
149 const first = data?.pages[0];
150 const state = first?.inventoryStatus.state;
151 if (state === "initializing" || state === "rebuilding") {122 if (state === "initializing" || state === "rebuilding") {
152 return 2_000;123 return 2_000;
153 }124 }
@@ -156,106 +127,8 @@ export function useWorkspaceTools( · 2 unmodified lines
156 placeholderData: (previousData) => previousData,127 placeholderData: (previousData) => previousData,
157 });128 });
158 129
159 const pages = inventoryQuery.data?.pages ?? [];130 const inventoryData = inventoryQuery.data;
160 const inventoryData = pages[0];131 const tools = inventoryData?.tools ?? [];
161 const tools = useMemo(() => {
162 const merged = new Map<string, ToolDescriptor>();
163 for (const page of pages) {
164 for (const tool of page.tools) {
165 merged.set(tool.path, tool);
166 }
167 }
168 for (const sourcePage of Object.values(sourcePages)) {
169 for (const tool of sourcePage.tools) {
170 merged.set(tool.path, tool);
171 }
172 }
173 return [...merged.values()];
174 }, [pages, sourcePages]);
175
176 const loadMoreToolsForSource = useCallback(async (
177 source: { source: string; sourceName: string },
178 ): Promise<void> => {
179 if (!context) {
180 return;
181 }
182
183 const current = sourcePages[source.sourceName];
184 if (current?.loading || current?.nextCursor === null) {
185 return;
186 }
187
188 setSourcePages((prev) => ({
189 ...prev,
190 [source.sourceName]: {
191 tools: prev[source.sourceName]?.tools ?? [],
192 nextCursor: prev[source.sourceName]?.nextCursor,
193 buildId: prev[source.sourceName]?.buildId,
194 loading: true,
195 },
196 }));
197
198 try {
199 const response = await listToolsWithWarnings({
200 workspaceId: context.workspaceId,
201 ...(context.accountId && { accountId: context.accountId }),
202 ...(context.clientId && { clientId: context.clientId }),
203 ...(context.sessionId && { sessionId: context.sessionId }),
204 includeDetails,
205 source: source.source,
206 sourceName: source.sourceName,
207 limit: DEFAULT_PAGE_SIZE,
208 ...(current?.nextCursor ? { cursor: current.nextCursor } : {}),
209 ...(current?.buildId ? { buildId: current.buildId } : {}),
210 });
211
212 setSourcePages((prev) => {
213 const existingTools = prev[source.sourceName]?.tools ?? [];
214 const merged = new Map<string, ToolDescriptor>();
215 for (const tool of existingTools) {
216 merged.set(tool.path, tool);
217 }
218 for (const tool of response.tools) {
219 merged.set(tool.path, tool);
220 }
221 return {
222 ...prev,
223 [source.sourceName]: {
224 tools: [...merged.values()],
225 nextCursor: response.nextCursor ?? null,
226 buildId: response.inventoryStatus.readyBuildId,
227 loading: false,
228 },
229 };
230 });
231 } catch {
232 setSourcePages((prev) => ({
233 ...prev,
234 [source.sourceName]: {
235 tools: prev[source.sourceName]?.tools ?? [],
236 nextCursor: prev[source.sourceName]?.nextCursor ?? null,
237 buildId: prev[source.sourceName]?.buildId,
238 loading: false,
239 },
240 }));
241 }
242 }, [context, includeDetails, listToolsWithWarnings, sourcePages]);
243
244 const sourceLoadingMoreTools = useMemo(() => {
245 const result: Record<string, boolean> = {};
246 for (const [sourceName, state] of Object.entries(sourcePages)) {
247 result[sourceName] = state.loading;
248 }
249 return result;
250 }, [sourcePages]);
251
252 const sourceHasMoreTools = useMemo(() => {
253 const result: Record<string, boolean> = {};
254 for (const [sourceName, state] of Object.entries(sourcePages)) {
255 result[sourceName] = state.nextCursor !== null;
256 }
257 return result;
258 }, [sourcePages]);
259 132
260 const loadToolDetails = useCallback(async (toolPaths: string[]): Promise<Record<string, ToolDescriptor>> => {133 const loadToolDetails = useCallback(async (toolPaths: string[]): Promise<Record<string, ToolDescriptor>> => {
261 const requested = [...new Set(toolPaths.filter((path) => path.length > 0))];134 const requested = [...new Set(toolPaths.filter((path) => path.length > 0))];
@@ -299,16 +172,6 @@ export function useWorkspaceTools( · 37 unmodified lines
299 detailsCacheRef.current.clear();172 detailsCacheRef.current.clear();
300 }, [context?.workspaceId, context?.accountId, context?.clientId, context?.sessionId]);173 }, [context?.workspaceId, context?.accountId, context?.clientId, context?.sessionId]);
301 174
302 useEffect(() => {
303 setSourcePages({});
304 }, [
305 context?.workspaceId,
306 context?.accountId,
307 context?.clientId,
308 context?.sessionId,
309 inventoryData?.inventoryStatus.readyBuildId,
310 ]);
311
312 useEffect(() => {175 useEffect(() => {
313 if (!inventoryData || !includeDetails) {176 if (!inventoryData || !includeDetails) {
314 return;177 return;
@@ -320,11 +183,12 @@ export function useWorkspaceTools( · 5 unmodified lines
320 }, [inventoryData, includeDetails]);183 }, [inventoryData, includeDetails]);
321 184
322 const loadMoreTools = useCallback(async () => {185 const loadMoreTools = useCallback(async () => {
323 if (!inventoryQuery.hasNextPage || inventoryQuery.isFetchingNextPage) {186 return;
324 return;187 }, []);
325 }188
326 await inventoryQuery.fetchNextPage();189 const loadMoreToolsForSource = useCallback(async () => {
327 }, [inventoryQuery]);190 return;
191 }, []);
328 192
329 return {193 return {
330 tools,194 tools,
@@ -338,11 +202,11 @@ export function useWorkspaceTools( · 7 unmodified lines
338 loadingSources: inventoryData?.inventoryStatus.loadingSourceNames ?? [],202 loadingSources: inventoryData?.inventoryStatus.loadingSourceNames ?? [],
339 loadingTools: !!context && inventoryQuery.isLoading,203 loadingTools: !!context && inventoryQuery.isLoading,
340 refreshingTools: !!context && inventoryQuery.isFetching,204 refreshingTools: !!context && inventoryQuery.isFetching,
341 loadingMoreTools: inventoryQuery.isFetchingNextPage,205 loadingMoreTools: false,
342 hasMoreTools: Boolean(inventoryQuery.hasNextPage),206 hasMoreTools: false,
343 loadMoreTools,207 loadMoreTools,
344 sourceHasMoreTools,208 sourceHasMoreTools: {} as Record<string, boolean>,
345 sourceLoadingMoreTools,209 sourceLoadingMoreTools: {} as Record<string, boolean>,
346 loadMoreToolsForSource,210 loadMoreToolsForSource,
347 totalTools: inventoryData?.totalTools ?? tools.length,211 totalTools: inventoryData?.totalTools ?? tools.length,
348 loadedTools: tools.length,212 loadedTools: tools.length,
executor/packages/database/convex/_generated/api.d.ts+1−0
@@ -138,6 +138,7 @@ export declare const api: { · 137 unmodified lines
138 buildId?: string;138 buildId?: string;
139 clientId?: string;139 clientId?: string;
140 cursor?: string;140 cursor?: string;
141 fetchAll?: boolean;
141 includeDetails?: boolean;142 includeDetails?: boolean;
142 includeSourceMeta?: boolean;143 includeSourceMeta?: boolean;
143 limit?: number;144 limit?: number;
executor/packages/database/convex/executorNode.ts+2−0
@@ -36,6 +36,7 @@ export const listToolsWithWarnings = customAction({ · 35 unmodified lines
36 cursor: v.optional(v.string()),36 cursor: v.optional(v.string()),
37 limit: v.optional(v.number()),37 limit: v.optional(v.number()),
38 buildId: v.optional(v.string()),38 buildId: v.optional(v.string()),
39 fetchAll: v.optional(v.boolean()),
39 },40 },
40 handler: async (41 handler: async (
41 ctx,42 ctx,
@@ -69,6 +70,7 @@ export const listToolsWithWarnings = customAction({ · 27 unmodified lines
69 cursor: args.cursor,70 cursor: args.cursor,
70 limit: args.limit,71 limit: args.limit,
71 buildId: args.buildId,72 buildId: args.buildId,
73 fetchAll: args.fetchAll,
72 });74 });
73 75
74 return inventory;76 return inventory;
executor/packages/database/src/runtime/workspace_tools.ts+1−0
@@ -1279,6 +1279,7 @@ export async function listToolsWithWarningsForContext( · 1278 unmodified lines
1279 cursor?: string;1279 cursor?: string;
1280 limit?: number;1280 limit?: number;
1281 buildId?: string;1281 buildId?: string;
1282 fetchAll?: boolean;
1282 } = {},1283 } = {},
1283): Promise<{1284): Promise<{
1284 tools: ToolDescriptor[];1285 tools: ToolDescriptor[];