mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(frontend/library): Add toast on agent execution request failure (#9689)
Currently, when an agent execution fails to be executed, the front-end does not display any feedback to the user. The scope of this change is providing that. ### Changes 🏗️ * Extracted `useToastOnFail` from `credits` page into a unified helper method. * Uses `useToastOnFail` on agent execution requests on library pages. <img width="1000" alt="image" src="https://github.com/user-attachments/assets/2daa0597-eb93-457d-8887-0f00c4db89ac" /> <img width="1000" alt="image" src="https://github.com/user-attachments/assets/1a541c98-fb95-424f-8ffe-972332b3ce01" /> ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Run agent with invalid input <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [ ] `.env.example` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details> --------- Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
This commit is contained in:
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { useToast, useToastOnFail } from "@/components/ui/use-toast";
|
||||
|
||||
import { RefundModal } from "./RefundModal";
|
||||
import { CreditTransaction } from "@/lib/autogpt-server-api";
|
||||
@@ -38,20 +38,7 @@ export default function CreditsPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const topupStatus = searchParams.get("topup") as "success" | "cancel" | null;
|
||||
const { toast } = useToast();
|
||||
|
||||
const toastOnFail = useCallback(
|
||||
(action: string, fn: () => Promise<any>) => {
|
||||
return fn().catch((e) => {
|
||||
toast({
|
||||
title: `Unable to ${action}`,
|
||||
description: e.message,
|
||||
variant: "destructive",
|
||||
duration: 10000,
|
||||
});
|
||||
});
|
||||
},
|
||||
[toast],
|
||||
);
|
||||
const toastOnFail = useToastOnFail();
|
||||
|
||||
const [isRefundModalOpen, setIsRefundModalOpen] = useState(false);
|
||||
const [topUpTransactions, setTopUpTransactions] = useState<
|
||||
@@ -63,42 +50,44 @@ export default function CreditsPage() {
|
||||
setIsRefundModalOpen(true);
|
||||
});
|
||||
};
|
||||
const refundCredits = async (transaction_key: string, reason: string) =>
|
||||
toastOnFail("refund transaction", async () => {
|
||||
const amount = await refundTopUp(transaction_key, reason);
|
||||
if (amount > 0) {
|
||||
toast({
|
||||
title: "Refund approved! 🎉",
|
||||
description: `Your refund has been automatically processed. Based on your remaining balance, the amount of ${formatCredits(amount)} will be credited to your account.`,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Refund Request Received",
|
||||
description:
|
||||
"We have received your refund request. A member of our team will review it and reach out via email shortly.",
|
||||
});
|
||||
}
|
||||
});
|
||||
const refundCredits = (transaction_key: string, reason: string) =>
|
||||
refundTopUp(transaction_key, reason)
|
||||
.then((amount) => {
|
||||
if (amount > 0) {
|
||||
toast({
|
||||
title: "Refund approved! 🎉",
|
||||
description: `Your refund has been automatically processed. Based on your remaining balance, the amount of ${formatCredits(amount)} will be credited to your account.`,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Refund Request Received",
|
||||
description:
|
||||
"We have received your refund request. A member of our team will review it and reach out via email shortly.",
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(toastOnFail("refund transaction"));
|
||||
|
||||
useEffect(() => {
|
||||
if (api && topupStatus === "success") {
|
||||
toastOnFail("fulfill checkout", () => api.fulfillCheckout());
|
||||
api.fulfillCheckout().catch(toastOnFail("fulfill checkout"));
|
||||
}
|
||||
}, [api, topupStatus, toastOnFail]);
|
||||
|
||||
const openBillingPortal = async () => {
|
||||
toastOnFail("open billing portal", async () => {
|
||||
const portal = await api.getUserPaymentPortalLink();
|
||||
router.push(portal.url);
|
||||
});
|
||||
};
|
||||
const openBillingPortal = () =>
|
||||
api
|
||||
.getUserPaymentPortalLink()
|
||||
.then((portal) => {
|
||||
router.push(portal.url);
|
||||
})
|
||||
.catch(toastOnFail("open billing portal"));
|
||||
|
||||
const submitTopUp = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget;
|
||||
const amount =
|
||||
parseInt(new FormData(form).get("topUpAmount") as string) * 100;
|
||||
toastOnFail("request top-up", () => requestTopUp(amount));
|
||||
requestTopUp(amount).catch(toastOnFail("request top-up"));
|
||||
};
|
||||
|
||||
const submitAutoTopUpConfig = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -107,11 +96,11 @@ export default function CreditsPage() {
|
||||
const formData = new FormData(form);
|
||||
const amount = parseInt(formData.get("topUpAmount") as string) * 100;
|
||||
const threshold = parseInt(formData.get("threshold") as string) * 100;
|
||||
toastOnFail("update auto top-up config", () =>
|
||||
updateAutoTopUpConfig(amount, threshold).then(() => {
|
||||
updateAutoTopUpConfig(amount, threshold)
|
||||
.then(() => {
|
||||
toast({ title: "Auto top-up config updated! 🎉" });
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch(toastOnFail("update auto top-up config"));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { IconRefresh, IconSquare } from "@/components/ui/icons";
|
||||
import { useToastOnFail } from "@/components/ui/use-toast";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
@@ -38,6 +39,8 @@ export default function AgentRunDetailsView({
|
||||
[run],
|
||||
);
|
||||
|
||||
const toastOnFail = useToastOnFail();
|
||||
|
||||
const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => {
|
||||
if (!run) return [];
|
||||
return [
|
||||
@@ -79,14 +82,16 @@ export default function AgentRunDetailsView({
|
||||
const runAgain = useCallback(
|
||||
() =>
|
||||
agentRunInputs &&
|
||||
api.executeGraph(
|
||||
graph.id,
|
||||
graph.version,
|
||||
Object.fromEntries(
|
||||
Object.entries(agentRunInputs).map(([k, v]) => [k, v.value]),
|
||||
),
|
||||
),
|
||||
[api, graph, agentRunInputs],
|
||||
api
|
||||
.executeGraph(
|
||||
graph.id,
|
||||
graph.version,
|
||||
Object.fromEntries(
|
||||
Object.entries(agentRunInputs).map(([k, v]) => [k, v.value]),
|
||||
),
|
||||
)
|
||||
.catch(toastOnFail("execute agent")),
|
||||
[api, graph, agentRunInputs, toastOnFail],
|
||||
);
|
||||
|
||||
const stopRun = useCallback(
|
||||
|
||||
@@ -7,6 +7,7 @@ import { GraphExecutionID, GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { LocalValuedInput } from "@/components/ui/input";
|
||||
import { useToastOnFail } from "@/components/ui/use-toast";
|
||||
import { Pencil2Icon } from "@radix-ui/react-icons";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { IconPlay } from "@/components/ui/icons";
|
||||
@@ -28,6 +29,7 @@ export default function AgentRunDraftView({
|
||||
agentActions: ButtonAction[];
|
||||
}): React.ReactNode {
|
||||
const api = useBackendAPI();
|
||||
const toastOnFail = useToastOnFail();
|
||||
|
||||
const agentInputs = graph.input_schema.properties;
|
||||
const [inputValues, setInputValues] = useState<Record<string, any>>({});
|
||||
@@ -57,8 +59,9 @@ export default function AgentRunDraftView({
|
||||
() =>
|
||||
api
|
||||
.executeGraph(graph.id, graph.version, inputValues)
|
||||
.then((newRun) => onRun(newRun.graph_exec_id)),
|
||||
[api, graph, inputValues, onRun],
|
||||
.then((newRun) => onRun(newRun.graph_exec_id))
|
||||
.catch(toastOnFail("execute agent")),
|
||||
[api, graph, inputValues, onRun, toastOnFail],
|
||||
);
|
||||
|
||||
const runActions: ButtonAction[] = useMemo(
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import type { ButtonAction } from "@/components/agptui/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { AgentRunStatus } from "@/components/agents/agent-run-status-chip";
|
||||
import { useToastOnFail } from "@/components/ui/use-toast";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
@@ -29,6 +30,8 @@ export default function AgentScheduleDetailsView({
|
||||
|
||||
const selectedRunStatus: AgentRunStatus = "scheduled";
|
||||
|
||||
const toastOnFail = useToastOnFail();
|
||||
|
||||
const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -67,8 +70,9 @@ export default function AgentScheduleDetailsView({
|
||||
() =>
|
||||
api
|
||||
.executeGraph(graph.id, graph.version, schedule.input_data)
|
||||
.then((run) => onForcedRun(run.graph_exec_id)),
|
||||
[api, graph, schedule, onForcedRun],
|
||||
.then((run) => onForcedRun(run.graph_exec_id))
|
||||
.catch(toastOnFail("execute agent")),
|
||||
[api, graph, schedule, onForcedRun, toastOnFail],
|
||||
);
|
||||
|
||||
const runActions: { label: string; callback: () => void }[] = useMemo(
|
||||
|
||||
@@ -188,4 +188,26 @@ function useToast() {
|
||||
};
|
||||
}
|
||||
|
||||
export { useToast, toast };
|
||||
interface ToastOnFailOptions {
|
||||
rethrow?: boolean;
|
||||
}
|
||||
|
||||
function useToastOnFail() {
|
||||
return React.useCallback(
|
||||
(action: string, { rethrow = false }: ToastOnFailOptions = {}) =>
|
||||
(error: any) => {
|
||||
toast({
|
||||
title: `Unable to ${action}`,
|
||||
description: (error as Error)?.message ?? "Something went wrong",
|
||||
variant: "destructive",
|
||||
duration: 10000,
|
||||
});
|
||||
if (rethrow) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
export { useToast, toast, useToastOnFail };
|
||||
|
||||
Reference in New Issue
Block a user