Fix FAL.AI models list endpoint (#4236) The FAL.AI endpoint seems to have switched to a paginated format where the results are nested under an `items` key. This updates the endpoint handler to fetch all pages and extract the model options correctly.

b45477578ffd3c135a9a4745fa3cf38af0db76f1

Jenner La Fave <jenner@jfave.com>

Signed
1 files changed, +32 -16Ignore whitespace
src/endpoints/stable-diffusion.js+32 -16
@@ -1160,28 +1160,44 @@ const falai = express.Router();
1160falai.post('/models', async (_request, response) => {1160falai.post('/models', async (_request, response) => {
1161 try {1161 try {
1162 const modelsUrl = new URL('https://fal.ai/api/models?categories=text-to-image');1162 const modelsUrl = new URL('https://fal.ai/api/models?categories=text-to-image');
1163 const result = await fetch(modelsUrl);1163 let page = 1;
1164 let modelsResponse;
1165 let models = [];
11641166
1165 if (!result.ok) {1167 do {
1166 console.warn('FAL.AI returned an error.', result.status, result.statusText);1168 modelsUrl.searchParams.set('page', page.toString());
1167 throw new Error('FAL.AI request failed.');1169 const result = await fetch(modelsUrl);
1168 }
11691170
1170 const data = await result.json();1171 if (!result.ok) {
1172 console.warn('FAL.AI returned an error.', result.status, result.statusText);
1173 throw new Error('FAL.AI request failed.');
1174 }
11711175
1172 if (!Array.isArray(data)) {
1173 console.warn('FAL.AI returned invalid data.');
1174 throw new Error('FAL.AI request failed.');
1175 }
11761176
1177 const models = data1177 modelsResponse = await result.json();
1178 .filter(x => !x.title.toLowerCase().includes('inpainting') &&1178 if (!('items' in modelsResponse) || !Array.isArray(modelsResponse.items)) {
1179 !x.title.toLowerCase().includes('control') &&1179 console.warn('FAL.AI returned invalid data.');
1180 !x.title.toLowerCase().includes('upscale') &&1180 throw new Error('FAL.AI request failed.');
1181 !x.title.toLowerCase().includes('lora'))1181 }
1182
1183 models = models.concat(
1184 modelsResponse.items.filter(
1185 x => (
1186 !x.title.toLowerCase().includes('inpainting') &&
1187 !x.title.toLowerCase().includes('control') &&
1188 !x.title.toLowerCase().includes('upscale') &&
1189 !x.title.toLowerCase().includes('lora')
1190 ),
1191 ),
1192 );
1193
1194 page = modelsResponse.page + 1;
1195 } while (modelsResponse != null && page < modelsResponse.pages);
1196
1197 const modelOptions = models
1182 .sort((a, b) => a.title.localeCompare(b.title))1198 .sort((a, b) => a.title.localeCompare(b.title))
1183 .map(x => ({ value: x.modelUrl.split('fal-ai/')[1], text: x.title }));1199 .map(x => ({ value: x.modelUrl.split('fal-ai/')[1], text: x.title }));
1184 return response.send(models);1200 return response.send(modelOptions);
1185 } catch (error) {1201 } catch (error) {
1186 console.error(error);1202 console.error(error);
1187 return response.sendStatus(500);1203 return response.sendStatus(500);