"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslations } from "next-intl"; import { Button, Card } from "@/shared/components"; import { useNotificationStore } from "@/store/notificationStore"; type CooldownItem = { provider: string; model: string; reason: string; remainingMs: number; unavailableSince: string; }; function formatRemaining(ms: number): string { const totalSec = Math.max(0, Math.ceil(ms / 1000)); const min = Math.floor(totalSec / 60); const sec = totalSec % 60; return `${min}m ${sec}s`; } export default function ModelCooldownsCard() { const t = useTranslations("settings"); const notify = useNotificationStore(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [busyKey, setBusyKey] = useState(null); const load = useCallback(async () => { try { const res = await fetch("/api/resilience/model-cooldowns", { cache: "no-store" }); const json = await res.json(); if (!res.ok) throw new Error(json?.error || `HTTP ${res.status}`); setItems(Array.isArray(json.items) ? json.items : []); } catch (error) { notify.error(error instanceof Error ? error.message : "Failed to load cooldowns"); } finally { setLoading(false); } }, [notify]); useEffect(() => { void load(); const timer = setInterval(() => { void load(); }, 5000); return () => clearInterval(timer); }, [load]); const clearOne = useCallback( async (provider: string, model: string) => { const key = `${provider}::${model}`; setBusyKey(key); try { const res = await fetch("/api/resilience/model-cooldowns", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, model }), }); const json = await res.json(); if (!res.ok) throw new Error(json?.error || `HTTP ${res.status}`); notify.success(`Model reactivated: ${provider}/${model}`); await load(); } catch (error) { notify.error(error instanceof Error ? error.message : "Failed to clear cooldown"); } finally { setBusyKey(null); } }, [load, notify] ); const clearAll = useCallback(async () => { setBusyKey("ALL"); try { const res = await fetch("/api/resilience/model-cooldowns", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ all: true }), }); const json = await res.json(); if (!res.ok) throw new Error(json?.error || `HTTP ${res.status}`); notify.success("All models in cooldown have been reactivated."); await load(); } catch (error) { notify.error(error instanceof Error ? error.message : "Failed to clear cooldowns"); } finally { setBusyKey(null); } }, [load, notify]); const hasItems = items.length > 0; const sorted = useMemo(() => [...items].sort((a, b) => b.remainingMs - a.remainingMs), [items]); return (

{t("modelCooldownsTitle")}

Models temporarily isolated after a failure. When the cooldown expires they come back automatically.

{loading ? (

Loading...

) : !hasItems ? (

{t("modelCooldownsEmpty")}

) : ( sorted.map((item) => { const rowKey = `${item.provider}::${item.model}`; return (

{item.provider}/{item.model}

reason: {item.reason} • remaining: {formatRemaining(item.remainingMs)}

); }) )}
); }