Browse Source

feat: add localStorage caching for model list and schema parameters

Adds 48-hour client-side caching to persist model data across dev server
restarts and page refreshes, eliminating repeated API calls during hot
reloading in development mode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
63a79ed91c
  1. 44
      src/components/modals/ModelSearchDialog.tsx
  2. 50
      src/components/nodes/ModelParameters.tsx

44
src/components/modals/ModelSearchDialog.tsx

@ -8,6 +8,38 @@ import { useReactFlow } from "@xyflow/react";
import { ProviderType, RecentModel } from "@/types";
import { ProviderModel, ModelCapability } from "@/lib/providers/types";
// localStorage cache for models (persists across dev server restarts)
const MODELS_CACHE_KEY = "node-banana-models-cache";
const MODELS_CACHE_TTL = 48 * 60 * 60 * 1000; // 48 hours
interface ModelsCacheEntry {
models: ProviderModel[];
timestamp: number;
}
function getCachedModels(cacheKey: string): ModelsCacheEntry | null {
try {
const cache = JSON.parse(localStorage.getItem(MODELS_CACHE_KEY) || "{}");
const entry = cache[cacheKey];
if (entry && Date.now() - entry.timestamp < MODELS_CACHE_TTL) {
return entry;
}
} catch {
// Ignore cache errors
}
return null;
}
function setCachedModels(cacheKey: string, models: ProviderModel[]) {
try {
const cache = JSON.parse(localStorage.getItem(MODELS_CACHE_KEY) || "{}");
cache[cacheKey] = { models, timestamp: Date.now() };
localStorage.setItem(MODELS_CACHE_KEY, JSON.stringify(cache));
} catch {
// Ignore cache errors
}
}
// Provider icons — all normalized to w-3.5 h-3.5 with viewBoxes cropped to fill consistently
const ReplicateIcon = () => (
<svg className="w-3.5 h-3.5" viewBox="0 0 1000 1000" fill="currentColor">
@ -139,6 +171,16 @@ export function ModelSearchDialog({
// Increment version to track this request
const thisVersion = ++requestVersionRef.current;
// Build cache key from filters
const cacheKey = `${providerFilter}:${capabilityFilter}:${debouncedSearch}`;
// Check localStorage cache first
const cached = getCachedModels(cacheKey);
if (cached) {
setModels(cached.models);
return;
}
setIsLoading(true);
setError(null);
@ -187,6 +229,8 @@ export function ModelSearchDialog({
if (data.success && data.models) {
setModels(data.models);
// Cache the successful result
setCachedModels(cacheKey, data.models);
} else {
setError(data.error || "Failed to fetch models");
setModels([]);

50
src/components/nodes/ModelParameters.tsx

@ -6,6 +6,40 @@ import { ModelParameter } from "@/lib/providers/types";
import { useProviderApiKeys } from "@/store/workflowStore";
import { deduplicatedFetch } from "@/utils/deduplicatedFetch";
// localStorage cache for model schemas (persists across dev server restarts)
const SCHEMA_CACHE_KEY = "node-banana-schema-cache";
const SCHEMA_CACHE_TTL = 48 * 60 * 60 * 1000; // 48 hours
interface SchemaCacheEntry {
parameters: ModelParameter[];
inputs: ModelInputDef[];
timestamp: number;
}
function getCachedSchema(modelId: string, provider: string): SchemaCacheEntry | null {
try {
const cache = JSON.parse(localStorage.getItem(SCHEMA_CACHE_KEY) || "{}");
const key = `${provider}:${modelId}`;
const entry = cache[key];
if (entry && Date.now() - entry.timestamp < SCHEMA_CACHE_TTL) {
return entry;
}
} catch {
// Ignore cache errors
}
return null;
}
function setCachedSchema(modelId: string, provider: string, parameters: ModelParameter[], inputs: ModelInputDef[]) {
try {
const cache = JSON.parse(localStorage.getItem(SCHEMA_CACHE_KEY) || "{}");
cache[`${provider}:${modelId}`] = { parameters, inputs, timestamp: Date.now() };
localStorage.setItem(SCHEMA_CACHE_KEY, JSON.stringify(cache));
} catch {
// Ignore cache errors
}
}
interface ModelParametersProps {
modelId: string;
provider: ProviderType;
@ -44,6 +78,14 @@ export function ModelParameters({
}
const fetchSchema = async () => {
// Check localStorage cache first
const cached = getCachedSchema(modelId, provider);
if (cached) {
setSchema(cached.parameters);
onInputsLoaded?.(cached.inputs);
return;
}
setIsLoading(true);
setError(null);
@ -75,11 +117,15 @@ export function ModelParameters({
const data = await response.json();
const params = data.parameters || [];
const inputs = data.inputs || [];
setSchema(params);
// Cache the successful result
setCachedSchema(modelId, provider, params, inputs);
// Pass inputs to parent for dynamic handle rendering
if (data.inputs && onInputsLoaded) {
onInputsLoaded(data.inputs);
if (onInputsLoaded) {
onInputsLoaded(inputs);
}
} catch (err) {
console.error("Failed to fetch model schema:", err);

Loading…
Cancel
Save