Browse Source

fix(03-01): add pagination to Replicate and fal.ai model fetching

Both APIs return paginated results but we were only fetching page 1.
Now fetches up to 5 pages from Replicate (~125 models) and 10 pages
from fal.ai to get more complete model lists.
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
69217d9a80
  1. 87
      src/app/api/models/route.ts

87
src/app/api/models/route.ts

@ -184,7 +184,8 @@ async function fetchReplicateModels(
apiKey: string, apiKey: string,
searchQuery?: string searchQuery?: string
): Promise<ProviderModel[]> { ): Promise<ProviderModel[]> {
let url: string; const allModels: ProviderModel[] = [];
let url: string | null;
let isSearchRequest = false; let isSearchRequest = false;
if (searchQuery) { if (searchQuery) {
@ -194,25 +195,38 @@ async function fetchReplicateModels(
url = `${REPLICATE_API_BASE}/models`; url = `${REPLICATE_API_BASE}/models`;
} }
const response = await fetch(url, { // Paginate through all results (limit to 5 pages = ~125 models to avoid timeout)
headers: { let pageCount = 0;
Authorization: `Bearer ${apiKey}`, const maxPages = 5;
},
});
if (!response.ok) { while (url && pageCount < maxPages) {
throw new Error(`Replicate API error: ${response.status}`); const response = await fetch(url, {
} headers: {
Authorization: `Bearer ${apiKey}`,
},
});
if (isSearchRequest) { if (!response.ok) {
const data: ReplicateSearchResponse = await response.json(); throw new Error(`Replicate API error: ${response.status}`);
if (!data.results) return []; }
return data.results.map((result) => mapReplicateModel(result.model));
} else { if (isSearchRequest) {
const data: ReplicateModelsResponse = await response.json(); const data: ReplicateSearchResponse = await response.json();
if (!data.results) return []; if (data.results) {
return data.results.map(mapReplicateModel); allModels.push(...data.results.map((result) => mapReplicateModel(result.model)));
}
url = data.next;
} else {
const data: ReplicateModelsResponse = await response.json();
if (data.results) {
allModels.push(...data.results.map(mapReplicateModel));
}
url = data.next;
}
pageCount++;
} }
return allModels;
} }
// ============ Fal.ai Helpers ============ // ============ Fal.ai Helpers ============
@ -245,26 +259,43 @@ async function fetchFalModels(
apiKey: string | null, apiKey: string | null,
searchQuery?: string searchQuery?: string
): Promise<ProviderModel[]> { ): Promise<ProviderModel[]> {
let url = `${FAL_API_BASE}/models?status=active`; const allModels: ProviderModel[] = [];
let cursor: string | null = null;
if (searchQuery) { let hasMore = true;
url += `&q=${encodeURIComponent(searchQuery)}`;
}
const headers: HeadersInit = {}; const headers: HeadersInit = {};
if (apiKey) { if (apiKey) {
headers["Authorization"] = `Key ${apiKey}`; headers["Authorization"] = `Key ${apiKey}`;
} }
const response = await fetch(url, { headers }); // Paginate through all results (limit to 10 pages to avoid timeout)
let pageCount = 0;
const maxPages = 10;
if (!response.ok) { while (hasMore && pageCount < maxPages) {
throw new Error(`fal.ai API error: ${response.status}`); let url = `${FAL_API_BASE}/models?status=active`;
} if (searchQuery) {
url += `&q=${encodeURIComponent(searchQuery)}`;
}
if (cursor) {
url += `&cursor=${encodeURIComponent(cursor)}`;
}
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`fal.ai API error: ${response.status}`);
}
const data: FalModelsResponse = await response.json();
allModels.push(...data.models.filter(isRelevantFalModel).map(mapFalModel));
const data: FalModelsResponse = await response.json(); cursor = data.next_cursor;
hasMore = data.has_more;
pageCount++;
}
return data.models.filter(isRelevantFalModel).map(mapFalModel); return allModels;
} }
// ============ Main Handler ============ // ============ Main Handler ============

Loading…
Cancel
Save