add better error handling in all components

This commit is contained in:
Abhimanyu Yadav
2025-05-28 17:27:07 +05:30
parent a135ba3f0b
commit c5e3148145
8 changed files with 357 additions and 181 deletions

View File

@@ -0,0 +1,58 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { AlertCircle, RefreshCw } from "lucide-react";
import React from "react";
interface ErrorStateProps {
title?: string;
message?: string;
error?: string | Error | null;
onRetry?: () => void;
retryLabel?: string;
className?: string;
showIcon?: boolean;
}
const ErrorState: React.FC<ErrorStateProps> = ({
title = "Something went wrong",
message,
error,
onRetry,
retryLabel = "Retry",
className,
showIcon = true,
}) => {
const errorMessage = error
? error instanceof Error
? error.message
: String(error)
: message || "An unexpected error occurred. Please try again.";
const classes =
"flex h-full w-full flex-col items-center justify-center text-center space-y-4";
return (
<div className={cn(classes, className)}>
{showIcon && <AlertCircle className="h-12 w-12" strokeWidth={1.5} />}
<div className="space-y-2">
<p className="text-sm font-medium text-zinc-800">{title}</p>
<p className="text-sm text-zinc-600">{errorMessage}</p>
</div>
{onRetry && (
<Button
variant="default"
size="sm"
onClick={onRetry}
className="mt-2 h-7 text-xs"
>
<RefreshCw className="mr-1 h-3 w-3" />
{retryLabel}
</Button>
)}
</div>
);
};
export default ErrorState;

View File

@@ -6,25 +6,36 @@ import { Skeleton } from "@/components/ui/skeleton";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { BlockCategoryResponse } from "@/lib/autogpt-server-api";
import { useBlockMenuContext } from "../block-menu-provider";
import ErrorState from "../ErrorState";
const AllBlocksContent: React.FC = () => {
const { addNode } = useBlockMenuContext();
const [categories, setCategories] = useState<BlockCategoryResponse[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [loadingCategories, setLoadingCategories] = useState<Set<string>>(
new Set(),
);
const api = useBackendAPI();
useEffect(() => {
const fetchBlocks = async () => {
const fetchBlocks = async () => {
try {
setLoading(true);
setError(null);
const response = await api.getBlockCategories();
setCategories(response);
} catch (err) {
console.error("Failed to fetch block categories:", err);
setError(
err instanceof Error ? err.message : "Failed to load block categories",
);
} finally {
setLoading(false);
};
}
};
useEffect(() => {
fetchBlocks();
}, [api]);
@@ -71,6 +82,18 @@ const AllBlocksContent: React.FC = () => {
);
}
if (error) {
return (
<div className="h-full p-4">
<ErrorState
title="Failed to load blocks"
error={error}
onRetry={fetchBlocks}
/>
</div>
);
}
return (
<div className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 transition-all duration-200 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200">
<div className="w-full space-y-3 px-4 pb-4">
@@ -93,7 +116,6 @@ const AllBlocksContent: React.FC = () => {
<div className="space-y-2">
{category.blocks.map((block, idx) => (
// It might go fail for agent block
<Block
key={`${category.name}-${idx}`}
title={block.name}

View File

@@ -1,30 +1,73 @@
import { Button } from "@/components/ui/button";
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, Fragment } from "react";
import IntegrationBlock from "../IntegrationBlock";
import { useBlockMenuContext } from "../block-menu-provider";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { Block } from "@/lib/autogpt-server-api";
import ErrorState from "../ErrorState";
import { Skeleton } from "@/components/ui/skeleton";
const IntegrationBlocks: React.FC = ({}) => {
const { integration, setIntegration, addNode } = useBlockMenuContext();
const [blocks, setBlocks] = useState<Block[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const api = useBackendAPI();
useEffect(() => {
const fetchBlocks = async () => {
if (integration) {
setIsLoading(true);
const fetchBlocks = async () => {
if (integration) {
try {
setLoading(true);
setError(null);
const response = await api.getBuilderBlocks({ provider: integration });
setBlocks(response.blocks);
setIsLoading(false);
} catch (err) {
console.error("Failed to fetch integration blocks:", err);
setError(
err instanceof Error
? err.message
: "Failed to load integration blocks",
);
} finally {
setLoading(false);
}
};
}
};
useEffect(() => {
fetchBlocks();
}, [api, integration]);
if (loading) {
return (
<div className="w-full space-y-3 p-4">
{[0, 1, 3].map((blockIndex) => (
<Fragment key={blockIndex}>
{blockIndex > 0 && (
<Skeleton className="my-4 h-[1px] w-full text-zinc-100" />
)}
{[0, 1, 2].map((index) => (
<IntegrationBlock.Skeleton key={`${blockIndex}-${index}`} />
))}
</Fragment>
))}
</div>
);
}
if (error) {
return (
<div className="h-full p-4">
<ErrorState
title="Failed to load integration blocks"
error={error}
onRetry={fetchBlocks}
/>
</div>
);
}
return (
<div className="space-y-2.5">
<div className="flex items-center justify-between">
@@ -49,30 +92,19 @@ const IntegrationBlocks: React.FC = ({}) => {
{blocks.length}
</span>
</div>
{isLoading ? (
<div className="space-y-3">
{Array(5)
.fill(0)
.map((_, index) => (
<IntegrationBlock.Skeleton key={index} />
))}
</div>
) : (
<div className="space-y-3">
{blocks.map((block, index) => (
<IntegrationBlock
key={index}
title={block.name}
description={block.description}
icon_url={`/integrations/${integration}.png`}
onClick={() => {
addNode(block);
}}
/>
))}
</div>
)}
<div className="space-y-3">
{blocks.map((block, index) => (
<IntegrationBlock
key={index}
title={block.name}
description={block.description}
icon_url={`/integrations/${integration}.png`}
onClick={() => {
addNode(block);
}}
/>
))}
</div>
</div>
);
};

View File

@@ -1,50 +1,59 @@
import React from "react";
import MarketplaceAgentBlock from "../MarketplaceAgentBlock";
import { Button } from "@/components/ui/button";
import { usePagination } from "@/hooks/usePagination";
import ErrorState from "../ErrorState";
const MarketplaceAgentsContent: React.FC = () => {
const { data: agents, loading, loadingMore, hasMore, error, scrollRef, refresh } = usePagination({
request: { apiType: 'store-agents' },
const {
data: agents,
loading,
loadingMore,
hasMore,
error,
scrollRef,
refresh,
} = usePagination({
request: { apiType: "store-agents" },
pageSize: 10,
});
if (loading) {
return (
<div className="w-full space-y-3 p-4">
{[0, 1, 2, 3, 4].map((index) => (
<MarketplaceAgentBlock.Skeleton key={index} />
))}
</div>
);
}
if (error) {
return (
<div className="h-full p-4">
<ErrorState
title="Failed to load marketplace agents"
error={error}
onRetry={refresh}
/>
</div>
);
}
return (
<div
<div
ref={scrollRef}
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200 transition-all duration-200"
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 transition-all duration-200 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200"
>
<div className="w-full space-y-3 px-4 pb-4">
{loading
? Array(5)
.fill(null)
.map((_, index) => (
<MarketplaceAgentBlock.Skeleton key={index} />
))
: agents.map((agent) => (
<MarketplaceAgentBlock
key={agent.slug}
title={agent.agent_name}
image_url={agent.agent_image}
creator_name={agent.creator}
number_of_runs={agent.runs}
/>
))}
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
<p className="text-sm text-red-600 mb-2">
Error loading marketplace agents: {error}
</p>
<Button
variant="outline"
size="sm"
onClick={refresh}
className="h-7 text-xs"
>
Retry
</Button>
</div>
)}
{agents.map((agent) => (
<MarketplaceAgentBlock
key={agent.slug}
title={agent.agent_name}
image_url={agent.agent_image}
creator_name={agent.creator}
number_of_runs={agent.runs}
/>
))}
{loadingMore && hasMore && (
<>
{Array.from({ length: 3 }).map((_, index) => (

View File

@@ -1,50 +1,59 @@
import React from "react";
import UGCAgentBlock from "../UGCAgentBlock";
import { Button } from "@/components/ui/button";
import { usePagination } from "@/hooks/usePagination";
import ErrorState from "../ErrorState";
const MyAgentsContent: React.FC = () => {
const { data: agents, loading, loadingMore, hasMore, error, scrollRef, refresh } = usePagination({
request: { apiType: 'library-agents' },
const {
data: agents,
loading,
loadingMore,
hasMore,
error,
scrollRef,
refresh,
} = usePagination({
request: { apiType: "library-agents" },
pageSize: 10,
});
if (loading) {
return (
<div className="w-full space-y-3 p-4">
{[0, 1, 2, 3, 4].map((index) => (
<UGCAgentBlock.Skeleton key={index} />
))}
</div>
);
}
if (error) {
return (
<div className="h-full p-4">
<ErrorState
title="Failed to load library agents"
error={error}
onRetry={refresh}
/>
</div>
);
}
return (
<div
<div
ref={scrollRef}
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200 transition-all duration-200"
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 transition-all duration-200 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200"
>
<div className="w-full space-y-3 px-4 pb-4">
{loading
? Array(5)
.fill(null)
.map((_, index) => (
<UGCAgentBlock.Skeleton key={index} />
))
: agents.map((agent) => (
<UGCAgentBlock
key={agent.id}
title={agent.name}
edited_time={agent.updated_at}
version={agent.graph_version}
image_url={agent.image_url}
/>
))}
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
<p className="text-sm text-red-600 mb-2">
Error loading library agents: {error}
</p>
<Button
variant="outline"
size="sm"
onClick={refresh}
className="h-7 text-xs"
>
Retry
</Button>
</div>
)}
{agents.map((agent) => (
<UGCAgentBlock
key={agent.id}
title={agent.name}
edited_time={agent.updated_at}
version={agent.graph_version}
image_url={agent.image_url}
/>
))}
{loadingMore && hasMore && (
<>
{Array.from({ length: 3 }).map((_, index) => (

View File

@@ -1,9 +1,9 @@
import React from "react";
import React, { Fragment } from "react";
import BlocksList from "./BlocksList";
import Block from "../Block";
import { Button } from "@/components/ui/button";
import { BlockRequest } from "@/lib/autogpt-server-api";
import { usePagination } from "@/hooks/usePagination";
import ErrorState from "../ErrorState";
interface PaginatedBlocksContentProps {
blockRequest: BlockRequest;
@@ -14,34 +14,54 @@ const PaginatedBlocksContent: React.FC<PaginatedBlocksContentProps> = ({
blockRequest,
pageSize = 10,
}) => {
const { data: blocks, loading, loadingMore, hasMore, error, scrollRef, refresh } = usePagination({
request: { apiType: 'blocks', ...blockRequest },
const {
data: blocks,
loading,
loadingMore,
hasMore,
error,
scrollRef,
refresh,
} = usePagination({
request: { apiType: "blocks", ...blockRequest },
pageSize,
});
if (loading) {
return (
<div className="w-full space-y-3 p-4">
{[0, 1, 3].map((categoryIndex) => (
<Fragment key={categoryIndex}>
{categoryIndex > 0 && (
<div className="my-4 h-[1px] w-full bg-zinc-100" />
)}
{[0, 1, 2].map((blockIndex) => (
<Block.Skeleton key={`${categoryIndex}-${blockIndex}`} />
))}
</Fragment>
))}
</div>
);
}
if (error) {
return (
<div className="w-full px-4 pb-4">
<ErrorState
title="Failed to load blocks"
error={error}
onRetry={refresh}
/>
</div>
);
}
return (
<div
<div
ref={scrollRef}
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200 transition-all duration-200"
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 transition-all duration-200 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200"
>
<BlocksList blocks={blocks} loading={loading} />
{error && (
<div className="w-full px-4 pb-4">
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
<p className="text-sm text-red-600 mb-2">
Error loading blocks: {error}
</p>
<Button
variant="outline"
size="sm"
onClick={refresh}
className="h-7 text-xs"
>
Retry
</Button>
</div>
</div>
)}
{loadingMore && hasMore && (
<div className="w-full space-y-3 px-4 pb-4">
{Array.from({ length: 3 }).map((_, index) => (
@@ -53,4 +73,4 @@ const PaginatedBlocksContent: React.FC<PaginatedBlocksContentProps> = ({
);
};
export default PaginatedBlocksContent;
export default PaginatedBlocksContent;

View File

@@ -1,54 +1,63 @@
import React from "react";
import Integration from "../Integration";
import { Button } from "@/components/ui/button";
import { useBlockMenuContext } from "../block-menu-provider";
import { usePagination } from "@/hooks/usePagination";
import ErrorState from "../ErrorState";
const PaginatedIntegrationList: React.FC = () => {
const { setIntegration } = useBlockMenuContext();
const { data: providers, loading, loadingMore, hasMore, error, scrollRef, refresh } = usePagination({
request: { apiType: 'providers' },
const {
data: providers,
loading,
loadingMore,
hasMore,
error,
scrollRef,
refresh,
} = usePagination({
request: { apiType: "providers" },
pageSize: 10,
});
if (loading) {
return (
<div className="w-full space-y-3 p-4">
{[0, 1, 3].map((integrationIndex) => (
<Integration.Skeleton key={integrationIndex} />
))}
</div>
);
}
if (error) {
return (
<div className="h-full p-4">
<ErrorState
title="Failed to load integrations"
error={error}
onRetry={refresh}
/>
</div>
);
}
return (
<div
<div
ref={scrollRef}
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200 transition-all duration-200"
className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 transition-all duration-200 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200"
>
<div className="w-full px-4 pb-4">
<div className="space-y-3">
{loading
? Array(5)
.fill(null)
.map((_, index) => (
<Integration.Skeleton key={index} />
))
: providers.map((integration, index) => (
<Integration
key={index}
title={integration.name}
icon_url={`/integrations/${integration.name}.png`}
description={integration.description}
number_of_blocks={integration.integration_count}
onClick={() => setIntegration(integration.name)}
/>
))}
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
<p className="text-sm text-red-600 mb-2">
Error loading integrations: {error}
</p>
<Button
variant="outline"
size="sm"
onClick={refresh}
className="h-7 text-xs"
>
Retry
</Button>
</div>
)}
{providers.map((integration, index) => (
<Integration
key={index}
title={integration.name}
icon_url={`/integrations/${integration.name}.png`}
description={integration.description}
number_of_blocks={integration.integration_count}
onClick={() => setIntegration(integration.name)}
/>
))}
{loadingMore && hasMore && (
<>
{Array.from({ length: 3 }).map((_, index) => (
@@ -62,4 +71,4 @@ const PaginatedIntegrationList: React.FC = () => {
);
};
export default PaginatedIntegrationList;
export default PaginatedIntegrationList;

View File

@@ -8,6 +8,7 @@ import {
SuggestionsResponse,
} from "@/lib/autogpt-server-api";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import ErrorState from "../ErrorState";
const SuggestionContent: React.FC = () => {
const { setIntegration, setDefaultState, setSearchQuery, addNode } =
@@ -16,27 +17,43 @@ const SuggestionContent: React.FC = () => {
const [suggestionsData, setSuggestionsData] =
useState<SuggestionsResponse | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const api = useBackendAPI();
useEffect(() => {
const fetchSuggestions = async () => {
try {
setLoading(true);
const response = await api.getSuggestions();
setSuggestionsData(response);
console.log(response);
setLoading(false);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
};
const fetchSuggestions = async () => {
try {
setLoading(true);
setError(null);
const response = await api.getSuggestions();
setSuggestionsData(response);
console.log(response);
} catch (err) {
console.error("Error fetching data:", err);
setError(
err instanceof Error ? err.message : "Failed to load suggestions",
);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchSuggestions();
}, [api]);
if (error) {
return (
<div className="h-full p-4">
<ErrorState
title="Failed to load suggestions"
error={error}
onRetry={fetchSuggestions}
/>
</div>
);
}
return (
<div className="scrollbar-thumb-rounded h-full overflow-y-auto pt-4 transition-all duration-200 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-transparent hover:scrollbar-thumb-zinc-200">
<div className="w-full space-y-6 pb-4">