mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
add pagination on search list
This commit is contained in:
@@ -1,15 +1,116 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState, useCallback, useRef } from "react";
|
||||
import FiltersList from "./FiltersList";
|
||||
import SearchList from "./SearchList";
|
||||
import { useBlockMenuContext } from "../block-menu-provider";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
|
||||
const BlockMenuSearch: React.FC = ({}) => {
|
||||
const { searchData } = useBlockMenuContext();
|
||||
const { searchData, searchQuery, searchId, setSearchData } =
|
||||
useBlockMenuContext();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [hasMore, setHasMore] = useState<boolean>(true);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [loadingMore, setLoadingMore] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const api = useBackendAPI();
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
const fetchSearchData = useCallback(
|
||||
async (pageNum: number, isLoadMore: boolean = false) => {
|
||||
if (isLoadMore) {
|
||||
setLoadingMore(true);
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.searchBlocks({
|
||||
search_query: searchQuery,
|
||||
search_id: searchId,
|
||||
page: pageNum,
|
||||
page_size: pageSize,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
if (isLoadMore) {
|
||||
setSearchData((prev) => [...prev, ...response.items]);
|
||||
} else {
|
||||
setSearchData(response.items);
|
||||
}
|
||||
|
||||
setHasMore(response.more_pages);
|
||||
setError(null);
|
||||
} catch (error) {
|
||||
console.error("Error fetching search data:", error);
|
||||
setError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to load search results",
|
||||
);
|
||||
if (!isLoadMore) {
|
||||
setPage(0);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setLoadingMore(false);
|
||||
}
|
||||
},
|
||||
[searchQuery, searchId, api, setSearchData, pageSize],
|
||||
);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
if (!scrollRef.current || loadingMore || !hasMore) return;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
|
||||
if (scrollTop + clientHeight >= scrollHeight - 100) {
|
||||
const nextPage = page + 1;
|
||||
setPage(nextPage);
|
||||
fetchSearchData(nextPage, true);
|
||||
}
|
||||
}, [loadingMore, hasMore, page, fetchSearchData]);
|
||||
|
||||
useEffect(() => {
|
||||
const scrollElement = scrollRef.current;
|
||||
if (scrollElement) {
|
||||
scrollElement.addEventListener("scroll", handleScroll);
|
||||
return () => scrollElement.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
}, [handleScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery) {
|
||||
setPage(0);
|
||||
setHasMore(true);
|
||||
setError(null);
|
||||
fetchSearchData(0, false);
|
||||
} else {
|
||||
setSearchData([]);
|
||||
setError(null);
|
||||
setPage(0);
|
||||
setHasMore(true);
|
||||
}
|
||||
}, [searchQuery, searchId, fetchSearchData, setSearchData]);
|
||||
|
||||
return (
|
||||
<div className="scrollbar-thumb-rounded h-full space-y-4 overflow-y-auto py-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-zinc-200">
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="scrollbar-thumb-rounded h-full space-y-4 overflow-y-auto py-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-zinc-200"
|
||||
>
|
||||
{searchData.length !== 0 && <FiltersList />}
|
||||
<SearchList />
|
||||
<SearchList
|
||||
isLoading={isLoading}
|
||||
loadingMore={loadingMore}
|
||||
hasMore={hasMore}
|
||||
error={error}
|
||||
onRetry={() => {
|
||||
setPage(0);
|
||||
setError(null);
|
||||
fetchSearchData(0, false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import MarketplaceAgentBlock from "../MarketplaceAgentBlock";
|
||||
import Block from "../Block";
|
||||
import UGCAgentBlock from "../UGCAgentBlock";
|
||||
@@ -6,13 +6,24 @@ import AiBlock from "./AiBlock";
|
||||
import IntegrationBlock from "../IntegrationBlock";
|
||||
import { SearchItem, useBlockMenuContext } from "../block-menu-provider";
|
||||
import NoSearchResult from "./NoSearchResult";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const SearchList = () => {
|
||||
const { searchQuery, addNode, searchId, searchData, setSearchData } =
|
||||
useBlockMenuContext();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const api = useBackendAPI();
|
||||
interface SearchListProps {
|
||||
isLoading: boolean;
|
||||
loadingMore: boolean;
|
||||
hasMore: boolean;
|
||||
error: string | null;
|
||||
onRetry: () => void;
|
||||
}
|
||||
|
||||
const SearchList: React.FC<SearchListProps> = ({
|
||||
isLoading,
|
||||
loadingMore,
|
||||
hasMore,
|
||||
error,
|
||||
onRetry,
|
||||
}) => {
|
||||
const { searchQuery, addNode, searchData } = useBlockMenuContext();
|
||||
|
||||
// Need to change it once, we got provider blocks
|
||||
const getBlockType = (item: any) => {
|
||||
@@ -31,25 +42,6 @@ const SearchList = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await api.searchBlocks({
|
||||
search_query: searchQuery,
|
||||
search_id: searchId,
|
||||
});
|
||||
setSearchData(response.items);
|
||||
} catch (error) {
|
||||
console.error("Error fetching search data:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [searchQuery, setSearchData]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-2.5 px-4">
|
||||
@@ -65,6 +57,26 @@ const SearchList = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="px-4">
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
||||
<p className="mb-2 text-sm text-red-600">
|
||||
Error loading search results: {error}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onRetry}
|
||||
className="h-7 text-xs"
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (searchData.length === 0) {
|
||||
return <NoSearchResult />;
|
||||
}
|
||||
@@ -131,6 +143,15 @@ const SearchList = () => {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
{loadingMore && hasMore && (
|
||||
<div className="space-y-2.5">
|
||||
{Array(3)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<Block.Skeleton key={`loading-more-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user