import { Button } from "../../components/ui/Button"; import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; import { AsyncResult } from "effect/reactivity"; import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; import type { EnvironmentId } from "@t3tools/contracts"; import { providerFailurePresentation } from "@t3tools/shared/orchestrationV2ThreadError"; import * as DateTime from "effect/DateTime"; import { useState } from "react"; import { View } from "react-native"; import { AppText as Text } from "../../components/ui/AppText"; import { useDeadlinePassed } from "../../lib/clock"; import { threadEnvironment } from "../../state/threads"; import { useAtomCommand } from "../../state/use-atom-command"; export function UsageLimitRecoveryCard({ thread, environmentId, }: { thread: EnvironmentThreadShell; environmentId: EnvironmentId; }) { const updateMetadata = useAtomCommand(threadEnvironment.updateMetadata); const [pending, setPending] = useState(false); const [error, setError] = useState(null); const resetAt = thread.runtime?.usageLimitResetAt ?? null; const canSchedule = resetAt !== null && Date.parse(resetAt) > Date.parse(thread.latestRun?.completedAt ?? thread.updatedAt); const resetPassed = useDeadlinePassed(resetAt === null ? null : Date.parse(resetAt)); const runId = thread.latestRun?.runId; const recovery = thread.limitRecovery; const scheduled = recovery?.runId === runId && recovery?.resetAt === resetAt && recovery?.autoResume; if ( thread.runtime?.status !== "failed" || thread.runtime.lastErrorClass !== "usage_limit" || !runId ) return null; const failurePresentation = providerFailurePresentation({ class: thread.runtime.lastErrorClass, code: thread.runtime.lastErrorCode ?? null, message: thread.runtime.lastError ?? "", }); const recoveryDescription = failurePresentation.kind === "provider_capacity" ? `${failurePresentation.title}. ${thread.runtime.lastError ?? ""}` : resetAt ? `Usage limit resets ${DateTime.toDateUtc(DateTime.makeUnsafe(resetAt)).toLocaleString()}.` : "The provider did not report a reset time. Retry manually when your limit is available."; const snoozed = recovery?.snooze === true && recovery.runId === runId && recovery.resetAt === resetAt && resetAt !== null && thread.snoozedUntil !== null && Date.parse(thread.snoozedUntil) === Date.parse(resetAt); async function toggle(action: "resume" | "snooze") { if (!resetAt || !runId || !canSchedule) return; if (action === "snooze" && !snoozed && Date.parse(resetAt) <= Date.now()) { setError("The reset time has passed. Retry the thread manually."); return; } setPending(true); setError(null); const result = await updateMetadata({ environmentId, input: { threadId: thread.id, limitRecovery: { runId, resetAt, ...(action === "resume" ? { autoResume: !scheduled } : { snooze: !snoozed }), }, }, }); setPending(false); if (AsyncResult.isFailure(result)) { const cause = squashAtomCommandFailure(result); setError(cause instanceof Error ? cause.message : "Could not change limit recovery."); } } return ( {recoveryDescription} {canSchedule ? (