Browse Source

fix(24-03): remove fal.ai pricing API calls causing 429 errors

handoff-20260429-1057
shrimbly 6 months ago
parent
commit
ac6e953d3b
  1. 17
      .planning/ROADMAP.md
  2. 15
      .planning/STATE.md
  3. 89
      src/app/api/models/route.ts

17
.planning/ROADMAP.md

@ -367,20 +367,21 @@ Plans:
- [x] 25-01: Template types, TemplateCard component, TemplateExplorerView grid layout
- [x] 25-02: Sidebar filters (search, category, tags), WelcomeModal integration
#### Phase 26: Template Preview Rendering
#### Phase 26: Template Preview Rendering
**Goal**: Visual preview of workflow templates showing node layout and connections before loading
**Depends on**: Phase 25
**Research**: Unlikely (React Flow rendering, existing patterns)
**Plans**: TBD
**Plans**: 1 plan
**Features:**
- Miniature workflow visualization in preview panel
- Show nodes, connections, and overall structure
- Non-interactive preview (view only)
**Final Implementation:**
- Horizontal card layout with thumbnail image, details, and "Use workflow" button
- Two-column grid layout for better scanning
- Conditional dialog width (6xl for explorer, 2xl for other views)
- Direct workflow loading without intermediate modal
Plans:
- [ ] 26-01: TBD (run /gsd:plan-phase 26 to break down)
- [x] 26-01: Horizontal cards with direct workflow loading, conditional dialog sizing
#### Phase 27: Node Defaults Infrastructure
@ -456,7 +457,7 @@ Phases execute in numeric order: 1 → 2 → ... → 24 → 25 → 26 → 27 →
| 23. Model Browser Improvements | v1.2 | 1/1 | Complete | 2026-01-13 |
| 24. Improved Cost Summary | v1.2 | 2/3 | In progress | - |
| 25. Template Explorer UI | v1.3 | 2/2 | Complete | 2026-01-16 |
| 26. Template Preview Rendering | v1.3 | 0/? | Not started | - |
| 26. Template Preview Rendering | v1.3 | 1/1 | Complete | 2026-01-17 |
| 27. Node Defaults Infrastructure | v1.3 | 0/? | Not started | - |
| 28. Node Defaults UI | v1.3 | 0/? | Not started | - |
| 29. Canvas Performance | v1.3 | 0/? | Not started | - |

15
.planning/STATE.md

@ -9,12 +9,12 @@ See: .planning/PROJECT.md (updated 2026-01-09)
## Current Position
Phase: 25 of 29 (Template Explorer UI)
Plan: 2 of 2 in current phase
Phase: 26 of 29 (Template Preview Rendering)
Plan: 1 of 1 in current phase
Status: Complete
Last activity: 2026-01-16 - Completed 25-02-PLAN.md
Last activity: 2026-01-17 - Completed 26-01-PLAN.md
Progress: █████████░ 90%
Progress: █████████░ 92%
## Performance Metrics
@ -49,6 +49,7 @@ Progress: █████████░ 90%
| 20. Integration Tests | 2/2 | 11 min | 5.5 min |
| 24. Improved Cost Summary | 2/3 | 10 min | 5 min |
| 25. Template Explorer UI | 2/2 | 23 min | 11.5 min |
| 26. Template Preview Rendering | 1/1 | 25 min | 25 min |
**Recent Trend:**
- Last 5 plans: 5 min, 9 min, 2 min, 8 min, 15 min
@ -152,7 +153,7 @@ Recent decisions affecting current work:
## Session Continuity
Last session: 2026-01-16
Stopped at: Completed 25-02-PLAN.md (sidebar filters and WelcomeModal integration)
Last session: 2026-01-17
Stopped at: Completed 26-01-PLAN.md (horizontal cards with direct workflow loading)
Resume file: None
Next action: Execute 24-03-PLAN.md (CostDialog UI) or Phase 26 (Template Preview Rendering)
Next action: Execute 24-03-PLAN.md (CostDialog UI) or Phase 27 (Node Defaults Infrastructure)

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

@ -124,18 +124,6 @@ interface FalModel {
openapi?: Record<string, unknown>;
}
interface FalPricingResponse {
prices: FalPrice[];
has_more: boolean;
next_cursor: string | null;
}
interface FalPrice {
endpoint_id: string;
unit_price: number;
unit: string; // "image", "video", "second", etc.
currency: string;
}
// ============ Response Types ============
@ -293,68 +281,6 @@ function mapFalModel(model: FalModel): ProviderModel {
};
}
/**
* Fetch pricing for a list of fal.ai endpoint IDs
* Returns a Map of endpoint_id -> pricing info
* Best-effort: errors are logged but don't fail the request
*/
async function fetchFalPricing(
endpointIds: string[],
apiKey: string | null
): Promise<Map<string, ProviderModel["pricing"]>> {
const pricingMap = new Map<string, ProviderModel["pricing"]>();
if (endpointIds.length === 0) {
return pricingMap;
}
const headers: HeadersInit = {};
if (apiKey) {
headers["Authorization"] = `Key ${apiKey}`;
}
try {
// Batch endpoint IDs (API supports up to ~50 at once based on URL length limits)
const batchSize = 50;
for (let i = 0; i < endpointIds.length; i += batchSize) {
const batch = endpointIds.slice(i, i + batchSize);
const endpointIdsParam = batch.join(",");
const url = `${FAL_API_BASE}/models/pricing?endpoint_id=${encodeURIComponent(endpointIdsParam)}`;
const response = await fetch(url, { headers });
if (!response.ok) {
console.warn(`[Models] fal.ai pricing API error: ${response.status}`);
continue;
}
const data: FalPricingResponse = await response.json();
for (const price of data.prices) {
// Map fal.ai units to our pricing type
// "image" -> per-run (single generation)
// "video", "second" -> per-second (duration-based)
const pricingType: "per-run" | "per-second" =
price.unit === "image" ? "per-run" : "per-second";
pricingMap.set(price.endpoint_id, {
type: pricingType,
amount: price.unit_price,
currency: price.currency,
});
}
}
} catch (error) {
// Best-effort: log warning but don't fail
console.warn(
`[Models] Failed to fetch fal.ai pricing:`,
error instanceof Error ? error.message : "Unknown error"
);
}
return pricingMap;
}
async function fetchFalModels(
apiKey: string | null,
searchQuery?: string
@ -395,19 +321,8 @@ async function fetchFalModels(
pageCount++;
}
// Fetch pricing for all models (best-effort)
if (allModels.length > 0) {
const endpointIds = allModels.map((m) => m.id);
const pricingMap = await fetchFalPricing(endpointIds, apiKey);
// Merge pricing into models
for (const model of allModels) {
const pricing = pricingMap.get(model.id);
if (pricing) {
model.pricing = pricing;
}
}
}
// Note: Pricing not fetched - external provider pricing is unreliable
// CostDialog shows model links instead of prices for fal.ai/Replicate
return allModels;
}

Loading…
Cancel
Save