import { useCanvasStore } from "../store/useCanvasStore"; import { askCopilot } from "../store/useCopilotStore"; import { useAuthStore } from "../store/useAuthStore"; import { hubUrl, isUsable } from "../lib/hub"; import type { CanvasNodeData, NodeDef, SpaceEntry } from "../lib/types"; interface Action { label: string; run?: () => void; href?: string; } const BILLING = "https://huggingface.co/settings/billing"; /** The one tap that could fix a Space's problem: wake it, sign in, grant access, or go see it on the Hub. */ export function spaceAction(spaceId: string | null, entry: SpaceEntry | undefined, message: string): Action | null { const s = useCanvasStore.getState(); const auth = useAuthStore.getState(); const msg = message.toLowerCase(); const stage = entry?.summary.stage; if (/private|not found|sign in|sign-in|grant access/.test(msg)) { if (!auth.token()) return { label: "Sign in", run: () => void auth.login() }; if (!auth.hasScope("read-repos")) return { label: "Grant access", run: () => void auth.reauth() }; return spaceId ? { label: "Open on Hub", href: hubUrl(spaceId) } : null; } if (/401|403|unauthori[sz]ed|forbidden|gated/.test(msg)) { if (!auth.token()) return { label: "Sign in", run: () => void auth.login() }; return { label: "Usage & billing", href: BILLING }; } if (/quota|zerogpu|gpu.*(limit|exceed|exhaust)|(limit|exceed|exhaust).*gpu|too many requests|429/.test(msg)) return { label: "See usage", href: BILLING }; if (spaceId && (stage === "SLEEPING" || stage === "APP_STARTING" || stage === "BUILDING" || /asleep|sleep|starting up|not running|wak(e|ing)|did not wake/.test(msg))) { return { label: stage === "SLEEPING" || !stage ? "Wake it" : "Wait for it", run: () => void s.wakeSpace(spaceId) }; } if (spaceId && stage && !isUsable(stage) && stage !== "UNKNOWN") return { label: "Open on Hub", href: hubUrl(spaceId) }; if (spaceId && /crashed|paused|stopped|broken|owner/.test(msg)) return { label: "Open on Hub", href: hubUrl(spaceId) }; if (spaceId && (/not loaded|could not reach|network|cors|disconnected|closed the stream|result stream|timed out/.test(msg) || entry?.status === "error")) { return { label: "Retry", run: () => void s.refreshSpace(spaceId) }; } return null; } /** One tap that could fix the error, derived from the message and the Space's state. */ export function errorAction(data: CanvasNodeData, def: NodeDef): Action | null { const spaceId = def.space?.id ?? (def.type.startsWith("space:") ? def.type.slice(6).split("/").slice(0, 2).join("/") : null); const entry = spaceId ? useCanvasStore.getState().spaces[spaceId] : undefined; return spaceAction(spaceId, entry, data.error ?? ""); } /** The red error box under a node: the message, the best next action, and "Ask copilot why". */ export function NodeError({ data, def, className = "" }: { data: CanvasNodeData; def: NodeDef; className?: string }) { if (data.status !== "error" || !data.error) return null; const action = errorAction(data, def); return (
{data.error}
{action && (action.href ? ( {action.label} ) : ( ))}
); }