2024-09-10 18:23:20 +02:00
|
|
|
import { useApiFilterArgs } from "@/hooks/use-api-filter";
|
2024-09-19 18:01:57 +02:00
|
|
|
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
2024-09-14 15:42:56 +02:00
|
|
|
import { SearchFilter, SearchQuery, SearchResult } from "@/types/search";
|
2024-06-23 22:58:00 +02:00
|
|
|
import SearchView from "@/views/search/SearchView";
|
|
|
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
2024-09-14 15:42:56 +02:00
|
|
|
import useSWRInfinite from "swr/infinite";
|
|
|
|
|
|
|
|
const API_LIMIT = 25;
|
2024-06-23 22:58:00 +02:00
|
|
|
|
2024-09-11 16:41:16 +02:00
|
|
|
export default function Explore() {
|
2024-06-23 22:58:00 +02:00
|
|
|
// search field handler
|
|
|
|
|
|
|
|
const [search, setSearch] = useState("");
|
|
|
|
|
2024-09-19 18:01:57 +02:00
|
|
|
const [searchFilter, setSearchFilter, searchSearchParams] =
|
|
|
|
useApiFilterArgs<SearchFilter>();
|
|
|
|
|
|
|
|
const searchTerm = useMemo(
|
|
|
|
() => searchSearchParams?.["query"] || "",
|
|
|
|
[searchSearchParams],
|
|
|
|
);
|
2024-06-23 22:58:00 +02:00
|
|
|
|
|
|
|
// search filter
|
|
|
|
|
2024-09-11 21:20:41 +02:00
|
|
|
const similaritySearch = useMemo(() => {
|
|
|
|
if (!searchTerm.includes("similarity:")) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return searchTerm.split(":")[1];
|
|
|
|
}, [searchTerm]);
|
2024-09-11 16:41:16 +02:00
|
|
|
|
2024-06-23 22:58:00 +02:00
|
|
|
// search api
|
|
|
|
|
2024-09-19 18:01:57 +02:00
|
|
|
useSearchEffect("query", (query) => {
|
|
|
|
setSearch(query);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
2024-09-11 16:41:16 +02:00
|
|
|
useSearchEffect("similarity_search_id", (similarityId) => {
|
2024-09-11 21:20:41 +02:00
|
|
|
setSearch(`similarity:${similarityId}`);
|
|
|
|
// @ts-expect-error we want to clear this
|
|
|
|
setSearchFilter({ ...searchFilter, similarity_search_id: undefined });
|
2024-09-12 16:46:29 +02:00
|
|
|
return false;
|
2024-09-11 16:41:16 +02:00
|
|
|
});
|
2024-06-23 22:58:00 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
2024-09-19 18:01:57 +02:00
|
|
|
if (!searchTerm && !search) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setSearchFilter({
|
|
|
|
...searchFilter,
|
|
|
|
query: search.length > 0 ? search : undefined,
|
|
|
|
});
|
|
|
|
// only update when search is updated
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-06-23 22:58:00 +02:00
|
|
|
}, [search]);
|
|
|
|
|
2024-09-14 15:42:56 +02:00
|
|
|
const searchQuery: SearchQuery = useMemo(() => {
|
2024-06-23 22:58:00 +02:00
|
|
|
if (similaritySearch) {
|
|
|
|
return [
|
|
|
|
"events/search",
|
|
|
|
{
|
2024-09-11 21:20:41 +02:00
|
|
|
query: similaritySearch,
|
2024-06-23 22:58:00 +02:00
|
|
|
cameras: searchSearchParams["cameras"],
|
|
|
|
labels: searchSearchParams["labels"],
|
2024-09-10 18:23:20 +02:00
|
|
|
sub_labels: searchSearchParams["subLabels"],
|
2024-06-23 22:58:00 +02:00
|
|
|
zones: searchSearchParams["zones"],
|
|
|
|
before: searchSearchParams["before"],
|
|
|
|
after: searchSearchParams["after"],
|
|
|
|
include_thumbnails: 0,
|
2024-09-09 21:45:19 +02:00
|
|
|
search_type: "similarity",
|
2024-06-23 22:58:00 +02:00
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-09-10 18:23:20 +02:00
|
|
|
if (searchTerm) {
|
|
|
|
return [
|
|
|
|
"events/search",
|
|
|
|
{
|
|
|
|
query: searchTerm,
|
|
|
|
cameras: searchSearchParams["cameras"],
|
|
|
|
labels: searchSearchParams["labels"],
|
|
|
|
sub_labels: searchSearchParams["subLabels"],
|
|
|
|
zones: searchSearchParams["zones"],
|
|
|
|
before: searchSearchParams["before"],
|
|
|
|
after: searchSearchParams["after"],
|
|
|
|
search_type: searchSearchParams["search_type"],
|
|
|
|
include_thumbnails: 0,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-09-11 16:41:16 +02:00
|
|
|
if (searchSearchParams && Object.keys(searchSearchParams).length !== 0) {
|
|
|
|
return [
|
|
|
|
"events",
|
|
|
|
{
|
|
|
|
cameras: searchSearchParams["cameras"],
|
|
|
|
labels: searchSearchParams["labels"],
|
|
|
|
sub_labels: searchSearchParams["subLabels"],
|
|
|
|
zones: searchSearchParams["zones"],
|
|
|
|
before: searchSearchParams["before"],
|
|
|
|
after: searchSearchParams["after"],
|
|
|
|
search_type: searchSearchParams["search_type"],
|
2024-09-14 15:42:56 +02:00
|
|
|
limit:
|
|
|
|
Object.keys(searchSearchParams).length == 0 ? API_LIMIT : undefined,
|
2024-09-11 16:41:16 +02:00
|
|
|
in_progress: 0,
|
|
|
|
include_thumbnails: 0,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2024-06-23 22:58:00 +02:00
|
|
|
}, [searchTerm, searchSearchParams, similaritySearch]);
|
|
|
|
|
2024-09-14 15:42:56 +02:00
|
|
|
// paging
|
|
|
|
|
|
|
|
const getKey = (
|
|
|
|
pageIndex: number,
|
|
|
|
previousPageData: SearchResult[] | null,
|
|
|
|
): SearchQuery => {
|
|
|
|
if (previousPageData && !previousPageData.length) return null; // reached the end
|
|
|
|
if (!searchQuery) return null;
|
|
|
|
|
|
|
|
const [url, params] = searchQuery;
|
|
|
|
|
|
|
|
// If it's not the first page, use the last item's start_time as the 'before' parameter
|
|
|
|
if (pageIndex > 0 && previousPageData) {
|
|
|
|
const lastDate = previousPageData[previousPageData.length - 1].start_time;
|
|
|
|
return [
|
|
|
|
url,
|
|
|
|
{ ...params, before: lastDate.toString(), limit: API_LIMIT },
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
// For the first page, use the original params
|
|
|
|
return [url, { ...params, limit: API_LIMIT }];
|
|
|
|
};
|
|
|
|
|
|
|
|
const { data, size, setSize, isValidating } = useSWRInfinite<SearchResult[]>(
|
|
|
|
getKey,
|
|
|
|
{
|
2024-09-18 20:18:16 +02:00
|
|
|
revalidateFirstPage: true,
|
2024-09-14 15:42:56 +02:00
|
|
|
revalidateAll: false,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const searchResults = useMemo(
|
|
|
|
() => (data ? ([] as SearchResult[]).concat(...data) : []),
|
|
|
|
[data],
|
|
|
|
);
|
|
|
|
const isLoadingInitialData = !data && !isValidating;
|
|
|
|
const isLoadingMore =
|
|
|
|
isLoadingInitialData ||
|
|
|
|
(size > 0 && data && typeof data[size - 1] === "undefined");
|
|
|
|
const isEmpty = data?.[0]?.length === 0;
|
|
|
|
const isReachingEnd =
|
|
|
|
isEmpty || (data && data[data.length - 1]?.length < API_LIMIT);
|
|
|
|
|
|
|
|
const loadMore = useCallback(() => {
|
|
|
|
if (!isReachingEnd && !isLoadingMore) {
|
|
|
|
if (searchQuery) {
|
|
|
|
const [url] = searchQuery;
|
|
|
|
|
|
|
|
// for chroma, only load 100 results for description and similarity
|
|
|
|
if (url === "events/search" && searchResults.length >= 100) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setSize(size + 1);
|
|
|
|
}
|
|
|
|
}, [isReachingEnd, isLoadingMore, setSize, size, searchResults, searchQuery]);
|
|
|
|
|
2024-09-19 18:01:57 +02:00
|
|
|
return (
|
|
|
|
<SearchView
|
|
|
|
search={search}
|
|
|
|
searchTerm={searchTerm}
|
|
|
|
searchFilter={searchFilter}
|
|
|
|
searchResults={searchResults}
|
|
|
|
isLoading={(isLoadingInitialData || isLoadingMore) ?? true}
|
|
|
|
setSearch={setSearch}
|
|
|
|
setSimilaritySearch={(search) => setSearch(`similarity:${search.id}`)}
|
|
|
|
setSearchFilter={setSearchFilter}
|
|
|
|
onUpdateFilter={setSearchFilter}
|
|
|
|
loadMore={loadMore}
|
|
|
|
hasMore={!isReachingEnd}
|
|
|
|
/>
|
2024-06-23 22:58:00 +02:00
|
|
|
);
|
|
|
|
}
|