diff --git a/src/app/[locale]/search/page.tsx b/src/app/[locale]/search/page.tsx index b1e48c3..b835e01 100644 --- a/src/app/[locale]/search/page.tsx +++ b/src/app/[locale]/search/page.tsx @@ -1,5 +1,6 @@ import { Assistant } from '@/components/assistant'; import { Hits } from '@/components/hits'; +import { NotFound } from '@/components/not-found'; import { SearchProvider } from '@/components/search-provider'; import { SearchBox } from '@/components/searchbox'; @@ -16,6 +17,7 @@ export default async function Search() { {/* */} + diff --git a/src/components/assistant.tsx b/src/components/assistant.tsx index e8428f2..ccf7c84 100644 --- a/src/components/assistant.tsx +++ b/src/components/assistant.tsx @@ -19,29 +19,21 @@ export const Assistant = ({ className }: AssistantProps) => { const hits = useHits(); const querying = useLoading(); const [query] = useQuery(); - const [seenHits, setSeenHits] = useState(false); - useEffect(() => { - if (hits.length > 0) { - setSeenHits(true); - } - }, [hits]); - - if (!seenHits) { - return null; - } return ( -
+

Assistant

- +
@@ -50,7 +42,7 @@ export const Assistant = ({ className }: AssistantProps) => { type AssistantListenerProps = { query: string; - querying: boolean; + querying?: boolean; hits: V1Hit[]; }; const AssistantListener = ({ @@ -59,8 +51,6 @@ const AssistantListener = ({ querying, }: AssistantListenerProps) => { const [summary, setSummary] = useState(''); - // Little hack to ensure we do not display the error message between assistant requests - const [disableErrors, setDisableErrors] = useState(true); const queryRef = useRef(query); useEffect(() => { @@ -68,7 +58,6 @@ const AssistantListener = ({ // This avoids doing a double-query in between the request-response from the query API. queryRef.current = query; setSummary(''); - setDisableErrors(true); }, [query]); const { refetch, status } = useStreamRequest( @@ -86,14 +75,13 @@ const AssistantListener = ({ useEffect(() => { refetchHandlerFromHits(hits, queryRef.current, refetch)?.(); - setDisableErrors(false); }, [hits, refetch]); - if ( - !querying && - !disableErrors && - (status === 'error' || (status === 'success' && summary.trim() === '')) - ) { + if (querying) { + return null; + } + + if (status === 'error' || (status === 'success' && summary.trim() === '')) { return ( ( + + + + + + + + + + +); diff --git a/src/components/not-found.tsx b/src/components/not-found.tsx new file mode 100644 index 0000000..f354bf6 --- /dev/null +++ b/src/components/not-found.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { useHits, useLoading, useQuery } from '@clinia/search-sdk-react'; +import { cn } from '@clinia-ui/react'; +import { NotFoundIcon } from './not-found-icon'; + +type NotFoundProps = { + className?: string; +}; +export const NotFound = ({ className }: NotFoundProps) => { + const [query] = useQuery(); + const hits = useHits(); + const loading = useLoading(); + + if (hits?.length > 0 || loading) { + return null; + } + + return ( +
+ +

+ No results found for '{query}' +

+

+ There aren't any results matching your query. This can happen when + you search for something overly specific, or something outside the scope + of your dataset. +

+
+ ); +}; diff --git a/src/components/search-provider.tsx b/src/components/search-provider.tsx index 4781fa9..414ddea 100644 --- a/src/components/search-provider.tsx +++ b/src/components/search-provider.tsx @@ -1,9 +1,15 @@ 'use client'; import { SearchRequest, SearchResponse } from '@/lib/client'; -import { PropsWithChildren, useCallback, useMemo } from 'react'; +import { + PropsWithChildren, + useCallback, + useEffect, + useMemo, + useRef, +} from 'react'; import { Host } from '@clinia/client-common'; -import client from '@clinia/client-datapartition'; +import datapartitionclient from '@clinia/client-datapartition'; import { SearchParameters, type SearchSDKOptions, @@ -30,22 +36,37 @@ const getHost = (): Host => { }; }; -const datapartitionClient = client( - 'clinia', - { - mode: 'BearerToken', - bearerToken: '', - }, - { - hosts: [getHost()], +type DatapartitionClient = ReturnType; +let datapartitionClient: DatapartitionClient; +const getClient = (): DatapartitionClient => { + if (!datapartitionClient) { + datapartitionClient = datapartitionclient( + 'clinia', + { + mode: 'BearerToken', + bearerToken: '', + }, + { + hosts: [getHost()], + } + ); } -); + + return datapartitionClient; +}; export const SearchProvider = ({ children, state }: SearchProviderProps) => { + const client = useRef( + // Dumb value, we're just setting this to avoid having undefined in the ref. + datapartitionclient('clinia', { mode: 'BearerToken', bearerToken: '' }) + ); + useEffect(() => { + // We set the client in a ref to avoid hydration errors (window not defined). + client.current = getClient(); + }, []); + const search: SearchSDKOptions['search'] = async (_collection, params) => { - const resp = await datapartitionClient.searchClient.query< - Record - >({ + return client.current.searchClient.query>({ partitionKey: 'clinia', collectionKey: 'articles', v1SearchParameters: { @@ -102,7 +123,6 @@ export const SearchProvider = ({ children, state }: SearchProviderProps) => { ], }, }); - return resp; }; const searchForFacets: SearchSDKOptions['searchForFacets'] =